webdevpuneet.com

Inline Add Row to Table — Editable New-Row Insert HTML CSS JS

Inline Add Row to Table · Tables · Plain HTML, CSS & JS · Live preview

CategoryTables

Copy & paste

Source Code

<div class="iar-card">
  <div class="iar-head">
    <h3>Contacts</h3>
    <button type="button" class="iar-add-btn" id="iarAddBtn">+ Add row</button>
  </div>
  <table class="iar-table">
    <thead><tr><th>Name</th><th>Email</th><th>Company</th><th></th></tr></thead>
    <tbody id="iarBody"></tbody>
  </table>
</div>

Extend with AI

Take This Snippet Further With AI

The AI prompt for this one is for Patreon supporters ($3 a month), along with a site with no ads. Supporters enter the passcode once and it is remembered in this browser.

· Become a supporter

What's included

Features

True inline row insertion
The new row renders directly in the table body — no modal, no separate form page.
Scoped Save/Cancel actions
Both buttons act only on the one open new-row form, with fresh listeners on every render.
Required-field validation before commit
Name and email are checked; the row won't push into data until both pass.
Inline error messaging
Each invalid field shows its own error text directly beneath it, clearing once corrected.
Basic email format check
A regex validates email shape beyond simply checking the field is non-empty.
Auto-focus on open
The first field focuses automatically so typing can start with no extra click.
Single-new-row guarantee
Only one add-row form can be open at a time via the addingNew flag.
Independent per-row delete
Existing rows carry their own delete action, composing cleanly alongside the add flow.

About this UI Snippet

Inline Add Row to Table — Editable Row Insert With Save, Cancel & Validation

Screenshot of the Inline Add Row to Table snippet rendered live

Sending a user to a separate form or modal just to add one row to a table breaks their flow and loses the context of the table they were already looking at. This snippet keeps the whole interaction in place: clicking "Add row" inserts a genuinely editable row directly into the table body, with real input fields, a Save/Cancel action pair scoped to that row, and required-field validation that blocks the commit until the data is valid.

A row that's temporarily a form

Clicking "+ Add row" sets an addingNew flag and re-renders, appending a special .iar-new-row row whose cells contain real <input> elements instead of text — the row briefly becomes a tiny form embedded in the table's own structure, rather than opening a modal that covers the table or navigating to a separate page. The moment Save or Cancel resolves it, the row either becomes a normal data row or disappears entirely.

Scoped Save and Cancel, not a page-level submit

The Save and Cancel buttons live only in that one new row's action cell and are wired up fresh every time the row renders — Cancel simply clears addingNew and re-renders without touching ROWS, while Save runs validation and, only if it passes, pushes the new row into the array. Neither button affects any other row, and there's never more than one new-row form open at a time, keeping the interaction simple to reason about.

Real validation before commit

saveNewRow() checks that the name field is non-empty and that the email field both has a value and matches a basic email pattern via validEmail(). Each failing field gets an .iar-invalid border treatment and an inline error message directly beneath it; if either check fails, the function returns early and the row stays in edit mode — the data never reaches the ROWS array until it satisfies both required-field rules, and errors clear automatically once a field becomes valid on the next Save attempt.

Auto-focus for immediate typing

When the new row renders, the name input receives focus automatically, so a user who just clicked "Add row" can start typing immediately without an extra click into the first field — a small detail that keeps the interaction feeling instant rather than requiring a second deliberate action.

Delete stays independent

Each existing data row also carries its own delete button, unrelated to the add-row flow, splicing that row out of ROWS and re-rendering — demonstrating that the add-row pattern composes cleanly alongside other per-row actions rather than requiring a separate editing mode for the whole table.

Customizing it

Add more required fields, swap the email regex for a stricter validator, or allow editing existing rows inline too (see editable table for that pattern). Pair with a row-drag-reorder table so newly added rows can be repositioned immediately.

Step by step

How to Use

  1. 1
    Paste HTML, CSS, and JSA three-row contacts table renders with an "+ Add row" button in the header.
  2. 2
    Click "+ Add row"A new editable row appears at the bottom with real input fields, and the name field auto-focuses.
  3. 3
    Click Save with empty fieldsName and email show inline validation errors and the row stays open for correction.
  4. 4
    Fill in valid data and SaveThe row commits into the table as a normal data row, and the form row disappears.
  5. 5
    Click "+ Add row" then CancelThe in-progress row is discarded with no changes to the table's data.
  6. 6
    Click × on any existing rowThat row is deleted immediately — independent of the add-row flow.

Real-world uses

Common Use Cases

Contact and CRM lists
Add a new contact without leaving the list view or opening a modal.
Team and roster management
Quickly add a team member row with required name/email validation.
Inventory and catalog tables
Add a new SKU row inline, validating required fields before it's added.
Settings and config tables
Add a new key/value or feature-flag row directly in place.
Combine with full inline editing
Pair with an editable table so both adding and editing rows stay in the table.
Learning scoped form state
A clear reference for a temporary per-row form embedded in a larger list, without modal complexity.
Related: Sticky Summary/Totals Row
See the Sticky Summary/Totals Row for a related tables pattern worth pairing with this one.

Got questions?

Frequently Asked Questions

saveNewRow() checks name.trim() is non-empty before considering the row valid, and sets a valid flag to false with an inline error message if it isn't. The function returns before reaching the ROWS.push(...) call whenever valid is false, so nothing is ever added to the underlying data until every required check passes.

Add an input with its own id and error div inside renderNewRow(), read and trim its value in saveNewRow(), add a validity check for it alongside the name and email checks (setting valid = false and an error message if it fails), and include the field in the object pushed to ROWS.

Combine this pattern with the editable table snippet's approach: give each existing row an "Edit" action that swaps its cells for inputs pre-filled with current values (reusing the same Save/Cancel and validation structure as the add-row form), and write the saved values back into that row's object in ROWS instead of pushing a new one.

Add an input event listener to the relevant field that runs the same check used in saveNewRow() (e.g. validEmail(emailInput.value)) and toggles the .iar-invalid class and error text live, rather than waiting for the Save click — the validation functions are already factored out and reusable for either timing.

Keep ROWS and an addingNew boolean in component state; render the extra form row conditionally when addingNew is true, with controlled inputs bound to local field state (or a small draft object). On Save, run the same validation, and if valid append to ROWS via your state setter and clear addingNew; on Cancel, just clear addingNew without touching ROWS.