Skip to content
Template Empire
Browse components

Table Inline Editing

Correct one record in place, with an isolated draft and a clear commit or cancel.

Record corrections: inventory
RecordItemOn handListedActions
Desk lampDesk lamp12Yes
Reading lightReading light5No

Edit one record at a time. Save commits the row; Cancel discards the draft.

Record corrections: bookings
RecordServiceSeatsConfirmedActions
Morning slotconsultation4Yes
Afternoon slotreview2No

Edit one record at a time. Save commits the row; Cancel discards the draft.

Customise

Code

const rows = [
  { id: "shelf-a", label: "Desk lamp", values: { name: "Desk lamp", stock: 12, listed: true } },
];

const columns = [
  { key: "name", label: "Item", type: "text" },
  { key: "stock", label: "On hand", type: "number", min: 0, max: 500, step: 1 },
  { key: "listed", label: "Listed", type: "checkbox" },
];

<TableInlineEditing
  caption="Inventory corrections"
  rows={rows}
  columns={columns}
  onCommit={handleCommit}
/>

Table Inline Editing presents caller-owned records in a semantic table and lets a person edit one row at a time without navigating away. Save sends a copied row draft to the parent's typed callback; Cancel discards that draft. The parent remains responsible for applying or persisting accepted values.

Each row has a stable unique id and a short accessible label. Columns use native text, number, select or checkbox controls for string, finite number and boolean values. Fields and actions include the row name, and focus moves into the editor then returns to that row's Edit action after Save or Cancel.

This component does not add sorting, filtering, paging, selection, bulk editing, asynchronous persistence or a validation framework. Number fields use native bounds and step; invalid drafts stay in edit mode with a clear status.

Install

Add the @te registry to your components.json once:

{
  "registries": {
    "@te": "https://templateempire.io/r/{name}.json"
  }
}

Then install the component:

npx shadcn@latest add @te/table-inline-editing

Usage

import { useState } from "react";
import { TableInlineEditing } from "@/components/table-inline-editing";
import type { TableInlineEditingChange, TableInlineEditingRow } from "@/components/table-inline-editing";

const initialRows: readonly TableInlineEditingRow[] = [
  { id: "record-1", label: "Desk lamp", values: { name: "Desk lamp", stock: 12, listed: true } },
];
const columns = [
  { key: "name", label: "Item", type: "text" as const, maxLength: 60 },
  { key: "stock", label: "On hand", type: "number" as const, min: 0, max: 500, step: 1 },
  { key: "listed", label: "Listed", type: "checkbox" as const },
];

export function InventoryCorrections() {
  const [rows, setRows] = useState(initialRows);
  const commit = ({ rowId, values }: TableInlineEditingChange) => {
    setRows((current) => current.map((row) => row.id === rowId
      ? { ...row, label: String(values.name ?? row.label), values: { ...values } }
      : row));
  };
  return <TableInlineEditing caption="Inventory records" rows={rows} columns={columns} onCommit={commit} />;
}

Props

PropTypeDefaultDescription
captionstringrequiredVisible semantic table caption.
rowsreadonly TableInlineEditingRow[]requiredRows with stable unique ids, a short row label and caller-owned primitive values. The parent updates this collection after a commit.
columnsreadonly TableInlineEditingColumn[]requiredOrdered text, number, select or checkbox editor definitions.
onCommit(change: TableInlineEditingChange) => voidrequiredReceives the row id and a fresh complete values object after a valid Save. No persistence or async result is implied.
classNamestringundefinedAdditional classes merged onto the table shell.

Customisation

The table, editors and focus rings use semantic surface, foreground, muted, border, input, primary and ring tokens. Consumers own surrounding form layout and the decision to accept or persist the typed commit.

<TableInlineEditing
  caption="Inventory records"
  rows={rows}
  columns={columns}
  onCommit={commit}
  className="max-w-5xl"
/>

Frequently asked questions

Does Save mutate my row objects?
No. The callback receives a new values object, and the component does not mutate the input rows. The parent decides how to replace or persist a record.
Can two rows be edited at once?
No. Only one isolated draft is open at a time; other Edit actions are disabled until that draft is saved or canceled.
Does this include paging, sorting or bulk edits?
No. It only handles one row's inline draft. Compose it with the table tools your data view needs.

Related components

Build a complete data viewCombine focused table tasks with the rest of your application interface.Explore Template Empire