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.
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.
ID | |||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|
| Ava | Thompson | Owner | Engineering | active | 22 | $45,000 | Jan 2, 2023 | 0% | |||
| Liam | Nguyen | Admin | Marketing | inactive | 25 | $47,137 | Jan 13, 2023 | 9% | |||
| Noah | Silva | Editor | Design | pending | 28 | $49,274 | Jan 24, 2023 | 18% | |||
| Emma | Carter | Viewer | Sales | active | 31 | $51,411 | Feb 4, 2023 | 27% | |||
| Olivia | Rossi | Owner | Support | inactive | 34 | $53,548 | Feb 15, 2023 | 36% | |||
| William | Walker | Admin | Engineering | pending | 37 | $55,685 | Feb 26, 2023 | 45% | |||
| Sophia | Patel | Editor | Marketing | active | 40 | $57,822 | Mar 9, 2023 | 54% | |||
| James | Muller | Viewer | Design | inactive | 43 | $59,959 | Mar 20, 2023 | 63% | |||
| Isabella | Park | Owner | Sales | pending | 46 | $62,096 | Mar 31, 2023 | 72% | |||
| Lucas | Reyes | Admin | Support | active | 49 | $64,233 | Apr 11, 2023 | 81% | |||
| Mia | Johansson | Editor | Engineering | inactive | 52 | $66,370 | Apr 22, 2023 | 90% | |||
| Benjamin | Costa | Viewer | Marketing | pending | 55 | $68,507 | May 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.
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))
)
},
})
Modal editing & creating
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().
ID | |||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|
| Ava | Thompson | Owner | Engineering | active | 22 | $45,000 | Jan 2, 2023 | 0% | |||
| Liam | Nguyen | Admin | Marketing | inactive | 25 | $47,137 | Jan 13, 2023 | 9% | |||
| Noah | Silva | Editor | Design | pending | 28 | $49,274 | Jan 24, 2023 | 18% | |||
| Emma | Carter | Viewer | Sales | active | 31 | $51,411 | Feb 4, 2023 | 27% | |||
| Olivia | Rossi | Owner | Support | inactive | 34 | $53,548 | Feb 15, 2023 | 36% | |||
| William | Walker | Admin | Engineering | pending | 37 | $55,685 | Feb 26, 2023 | 45% | |||
| Sophia | Patel | Editor | Marketing | active | 40 | $57,822 | Mar 9, 2023 | 54% | |||
| James | Muller | Viewer | Design | inactive | 43 | $59,959 | Mar 20, 2023 | 63% | |||
| Isabella | Park | Owner | Sales | pending | 46 | $62,096 | Mar 31, 2023 | 72% | |||
| Lucas | Reyes | Admin | Support | active | 49 | $64,233 | Apr 11, 2023 | 81% | |||
| Mia | Johansson | Editor | Engineering | inactive | 52 | $66,370 | Apr 22, 2023 | 90% | |||
| Benjamin | Costa | Viewer | Marketing | pending | 55 | $68,507 | May 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:
| Prop | Type | Default | Description |
|---|---|---|---|
enableEditing? | boolean | false | Enable 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 key | Type | Description |
|---|---|---|
enableEditing? | boolean | Allow editing this column (defaults to true when table editing is on). |
editVariant? | EditVariant | Inline editor variant. Defaults to "text". |
editSelectOptions? | DataTableFilterOption[] | Options for the "select" edit variant. |
renderEditCell? | (props: CellRenderProps<TData, TValue>) => React.ReactNode | Custom 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 | undefined | Validate an edited value; return an error message or undefined if valid. |