Shadcn React Table

Search documentation

Search the docs

Editing

Set enableEditing: true to make rows editable, then choose how editing is presented with editDisplayMode. Per-column behavior is configured through column meta.

Cell editing

editDisplayMode: "cell" is the default. Click a cell to edit it inline and commit the new value with onEditCellSave.

Editing — cell
GitHub
ID
Jan 2, 2023
0%
Jan 13, 2023
9%
Jan 24, 2023
18%
Feb 4, 2023
27%
Feb 15, 2023
36%
Feb 26, 2023
45%
Mar 9, 2023
54%
Mar 20, 2023
63%
Mar 31, 2023
72%
Apr 11, 2023
81%
Apr 22, 2023
90%
May 3, 2023
99%
$681,042
const table = useDataTable({
  data,
  columns,
  getRowId: (row) => row.id,
  enableEditing: true,
  editDisplayMode: "cell",
  onEditCellSave: ({ row, column, value }) => {
    setData((prev) =>
      prev.map((r) => (r.id === row.id ? { ...r, [column.id]: value } : r))
    )
  },
})

return <DataTable table={table} />

Row editing

editDisplayMode: "row" edits a whole row at once with save/cancel controls. Persist the row with onSaveRow, then call exit() to leave edit mode.

Editing — row
GitHub
ID
AvaThompsonOwnerEngineeringactive22$45,000Jan 2, 2023
0%
LiamNguyenAdminMarketinginactive25$47,137Jan 13, 2023
9%
NoahSilvaEditorDesignpending28$49,274Jan 24, 2023
18%
EmmaCarterViewerSalesactive31$51,411Feb 4, 2023
27%
OliviaRossiOwnerSupportinactive34$53,548Feb 15, 2023
36%
WilliamWalkerAdminEngineeringpending37$55,685Feb 26, 2023
45%
SophiaPatelEditorMarketingactive40$57,822Mar 9, 2023
54%
JamesMullerViewerDesigninactive43$59,959Mar 20, 2023
63%
IsabellaParkOwnerSalespending46$62,096Mar 31, 2023
72%
LucasReyesAdminSupportactive49$64,233Apr 11, 2023
81%
MiaJohanssonEditorEngineeringinactive52$66,370Apr 22, 2023
90%
BenjaminCostaViewerMarketingpending55$68,507May 3, 2023
99%
$681,042
const table = useDataTable({
  data,
  columns,
  getRowId: (row) => row.id,
  enableEditing: true,
  editDisplayMode: "row",
  onSaveRow: ({ row, values, exit }) => {
    setData((prev) =>
      prev.map((r) => (r.id === row.id ? { ...r, ...values } : r))
    )
    exit()
  },
})

return <DataTable table={table} />

Table editing

editDisplayMode: "table" keeps every editable cell in edit mode at all times, like a spreadsheet.

Editing — table
GitHub
ID
Jan 2, 2023
0%
Jan 13, 2023
9%
Jan 24, 2023
18%
Feb 4, 2023
27%
Feb 15, 2023
36%
Feb 26, 2023
45%
Mar 9, 2023
54%
Mar 20, 2023
63%
Mar 31, 2023
72%
Apr 11, 2023
81%
Apr 22, 2023
90%
May 3, 2023
99%
$681,042
const table = useDataTable({
  data,
  columns,
  getRowId: (row) => row.id,
  enableEditing: true,
  editDisplayMode: "table",
  onEditCellSave: ({ row, column, value }) => {
    setData((prev) =>
      prev.map((r) => (r.id === row.id ? { ...r, [column.id]: value } : r))
    )
  },
})

editDisplayMode: "modal" edits (and creates) rows in a dialog. Use onSaveRow for edits and onCreateRow for new rows; both receive exit() to close the dialog. Seed the create form with createRowDefaults, and open it from the toolbar via table.cnTable.beginCreate().

Editing — modal
GitHub
ID
AvaThompsonOwnerEngineeringactive22$45,000Jan 2, 2023
0%
LiamNguyenAdminMarketinginactive25$47,137Jan 13, 2023
9%
NoahSilvaEditorDesignpending28$49,274Jan 24, 2023
18%
EmmaCarterViewerSalesactive31$51,411Feb 4, 2023
27%
OliviaRossiOwnerSupportinactive34$53,548Feb 15, 2023
36%
WilliamWalkerAdminEngineeringpending37$55,685Feb 26, 2023
45%
SophiaPatelEditorMarketingactive40$57,822Mar 9, 2023
54%
JamesMullerViewerDesigninactive43$59,959Mar 20, 2023
63%
IsabellaParkOwnerSalespending46$62,096Mar 31, 2023
72%
LucasReyesAdminSupportactive49$64,233Apr 11, 2023
81%
MiaJohanssonEditorEngineeringinactive52$66,370Apr 22, 2023
90%
BenjaminCostaViewerMarketingpending55$68,507May 3, 2023
99%
$681,042
const table = useDataTable({
  data,
  columns,
  getRowId: (row) => row.id,
  enableEditing: true,
  editDisplayMode: "modal",
  createRowDefaults: { name: "", role: "member" },
  onSaveRow: ({ row, values, exit }) => {
    setData((prev) =>
      prev.map((r) => (r.id === row.id ? { ...r, ...values } : r))
    )
    exit()
  },
  onCreateRow: ({ values, exit }) => {
    setData((prev) => [...prev, { id: crypto.randomUUID(), ...values }])
    exit()
  },
  renderToolbarActions: ({ table }) => (
    <Button size="sm" onClick={() => table.cnTable.beginCreate()}>
      New row
    </Button>
  ),
})

Per-column editing config

Configure editors on each column's meta:

  • editVariant: "text" | "number" | "select" — the input type.
  • editSelectOptions — options for the "select" variant.
  • validate: (value) => string | undefined — return a message to block the save.
  • enableEditing: false — lock a single column.
const columns = [
  {
    accessorKey: "role",
    header: "Role",
    meta: {
      editVariant: "select",
      editSelectOptions: ["admin", "member", "viewer"],
    },
  },
  {
    accessorKey: "email",
    header: "Email",
    meta: {
      editVariant: "text",
      validate: (value) =>
        /.+@.+\..+/.test(value) ? undefined : "Enter a valid email",
    },
  },
  {
    accessorKey: "id",
    header: "ID",
    meta: { enableEditing: false },
  },
]

Custom editor

When editVariant isn't enough, supply your own editor with meta.renderEditCell. It replaces the built-in field while the cell/row is editing; drive the value through the table instance (setRowDraftValue for row/modal editing, or onEditCellSave for cell editing):

{
  accessorKey: "status",
  header: "Status",
  meta: {
    renderEditCell: ({ cell, table }) => (
      <MyStatusPicker
        value={cell.getValue() as string}
        onChange={(value) =>
          table.cnTable.setRowDraftValue(cell.column.id, value)
        }
      />
    ),
  },
}

Relevant options

useDataTable options that drive editing and creating:

PropTypeDefaultDescription
enableEditing?booleanfalseEnable inline editing.
editDisplayMode?EditDisplayMode"cell"How edits are surfaced. Default "cell".
createDisplayMode?CreateDisplayMode"modal"How the create form is surfaced, independent of `editDisplayMode`: `"modal"` (dialog, default), `"row"` (an inline editable row at the top of the table), or `"custom"` (you render it from `isCreating` + `rowDraft`).
createRowDefaults?Record<string, unknown>Default values for the create form, keyed by column id.
onEditCellSave?(props: { row: Row<TData> column: Column<TData, unknown> value: unknown table: DataTableInstance<TData> }) => void
onSaveRow?(props: { row: Row<TData> values: Record<string, unknown> table: DataTableInstance<TData> exit: () => void }) => void
onCreateRow?(props: { values: Record<string, unknown> table: DataTableInstance<TData> exit: () => void }) => void

Per-column, via columnDef.meta:

meta keyTypeDescription
enableEditing?booleanAllow editing this column (defaults to true when table editing is on).
editVariant?EditVariantInline editor variant. Defaults to "text".
editSelectOptions?DataTableFilterOption[]Options for the "select" edit variant.
renderEditCell?(props: CellRenderProps<TData, TValue>) => React.ReactNodeCustom inline editor for this column (escape hatch). Replaces the built-in editor while the cell/row is editing; drive the value via `table.cnTable` (`rowDraft`/`setRowDraftValue` or `onEditCellSave`).
validate?(value: unknown) => string | undefinedValidate an edited value; return an error message or undefined if valid.