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

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.

Build with AI

Build, Understand, Optimize, and Extend It With AI

Rather than tracing the row-as-form pattern by hand, paste this snippet's HTML, CSS, and JS into an AI coding assistant like Claude and ask it to explain exactly why saveNewRow() re-checks both required fields and returns early before ever calling ROWS.push, and why the Save/Cancel button listeners are re-attached on every render rather than attached once — what would break about repeated add-row cycles if they weren't. The same assistant can help you extend it — ask it to add more required fields with their own validation rules, add duplicate-detection (e.g. rejecting an email that already exists in ROWS), or convert existing rows to also be editable inline using the same form-row pattern this snippet already uses for adding. Treat the code less like a finished artifact and more like a starting point for a conversation.

Prompt to recreate it

Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:

text
Build a table with an inline "add row" feature in plain HTML, CSS, and JavaScript with no library — clicking a button inserts a real editable row directly into the table body, not a separate modal or form page.

Requirements:
- Keep the table's data in a plain array of row objects, and a boolean flag tracking whether a new-row form is currently open.
- An "+ Add row" button in the table header, when clicked, sets that flag true and re-renders the table so a special extra row appears at the bottom of the tbody containing real text input elements (one per column) instead of static text, with the first input automatically focused.
- That new row's last cell contains two buttons scoped only to it: Save and Cancel. Cancel simply clears the open-row flag and re-renders, discarding whatever was typed, without modifying the underlying data array at all.
- Save runs validation on the required fields (at minimum a non-empty name field and an email field that is both non-empty and matches a basic email address pattern) before doing anything else. If any required field fails validation, give that specific input an invalid visual style and show an inline error message directly under it, and do not add anything to the data array or close the row — leave it open so the user can fix the input and try Save again.
- Only when every required field passes validation should Save construct a new row object from the input values, push it into the underlying data array, clear the open-row flag, and re-render so the form row is replaced by a normal display row showing the newly added data.
- Give each existing (already-saved) row its own independent delete button that removes just that row from the data array and re-renders, entirely separate from the add-row flow.
- Ensure only one add-row form can be open at any time, and that re-rendering after every state change (open, cancel, save, delete) keeps the DOM and the underlying data array consistent.

Want to tighten it up first? Run this prompt through the AI Prompt Studio to score it across 8 quality dimensions, catch anti-patterns, and tune the wording for Claude, ChatGPT, or Gemini before you paste it in.

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>