Edit ModePreview
Inline text, select, and markdown editors for DataTable cells.
Overview
Inline edit mode turns DataTable into an editable grid. Pass a stable
getRowId, mark editable cells with meta.editable, and write committed values
back through inlineEdit.onCellCommit.
Only editable cells participate in inline edit mode. Text is the default editor.
Use getEditor only when a column should switch to select or markdown.
Text Cells
Text is the default editor for short string fields like emails, roles, or locations.
const noteTokens: InlineTokenMap = {
"clause-4.2": {
variant: "citation",
label: "§4.2(b)",
popover: {
snippet: "The indemnifying party shall hold harmless...",
sourceFileName: "Contract_v3.pdf",
sourceMimeType: "application/pdf",
},
},
"nda-final": {
variant: "file",
label: "NDA_Final.docx",
popover: {
fileName: "NDA_Final.docx",
mimeType:
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
fileSize: "2.4 MB",
uploadedAt: "2026-03-08",
},
},
"risk-score": {
variant: "custom",
label: "High Risk",
triggerRender: () => <Badge variant="warning">High Risk</Badge>,
popoverRender: () => <RiskSummary />,
},
};
const columns: ColumnDef<Employee>[] = [
{
header: "Name",
accessorKey: "name",
},
{
header: "Email",
accessorKey: "email",
meta: { editable: true },
},
{
header: "Role",
accessorKey: "role",
meta: { editable: true },
},
{
header: "Location",
accessorKey: "location",
meta: { editable: true },
},
];
<DataTable
columns={columns}
data={rows}
getRowId={(row) => row.id}
inlineEdit={{
onCellCommit: ({ rowId, columnId, value }) => {
updateRow(rowId, columnId, value);
},
}}
/>Select Cells
Use select editors when a cell should commit one value from a fixed set. selectProps.style controls whether the selected value renders as
plain text or as a badge.
const roleOptions = [
{ value: "Developer", label: "Developer" },
{ value: "Designer", label: "Designer" },
{ value: "Manager", label: "Manager" },
];
const departmentOptions = [
{ value: "Engineering", label: "Engineering" },
{ value: "Design", label: "Design" },
{ value: "Marketing", label: "Marketing" },
];
const statusOptions = [
{
value: "Active",
label: "Active",
badgeProps: { variant: "success" },
},
{
value: "Inactive",
label: "Inactive",
badgeProps: { variant: "destructive" },
},
{
value: "Pending",
label: "Pending",
badgeProps: { variant: "warning" },
},
];
const columns: ColumnDef<Employee>[] = [
{
header: "Name",
accessorKey: "name",
},
{
header: "Role",
accessorKey: "role",
meta: { editable: true },
},
{
header: "Department",
accessorKey: "department",
meta: { editable: true },
},
{
header: "Status",
accessorKey: "status",
meta: { editable: true },
cell: ({ row }) => <StatusBadge status={row.getValue("status")} />,
},
];
<DataTable
columns={columns}
data={rows}
getRowId={(row) => row.id}
inlineEdit={{
onCellCommit: handleCommit,
getEditor: (cell) => {
if (cell.column.id === "role") {
return {
type: "select",
options: roleOptions,
selectProps: { style: "text" },
};
}
if (cell.column.id === "department") {
return {
type: "select",
options: departmentOptions,
selectProps: { style: "text" },
};
}
if (cell.column.id === "status") {
return {
type: "select",
options: statusOptions,
selectProps: { style: "badge" },
};
}
return { type: "text" };
},
}}
/>Option Shape
options is an array of objects. value is the committed cell value, while
label is the text shown in the trigger and menu.
const statusOptions = [
{
value: "Active",
label: "Active",
badgeProps: { variant: "success" },
},
{
value: "Inactive",
label: "Inactive",
badgeProps: { variant: "destructive" },
},
];| Field | Type | Description |
|---|---|---|
value | string | Stored value committed back through onCellCommit |
label | string | Display label shown in the select UI |
disabled | boolean | Prevent this option from being selected |
badgeProps | DataTableInlineEditSelectBadgeProps | Per-option badge styling when selectProps.style is "badge" |
selectProps.badgeProps sets default badge styling for the whole editor.
option.badgeProps overrides that default for a specific option.
Markdown Cells
Use markdown editors for longer notes, annotations, or tokenized references.
markdownProps.tokens lets you resolve inline tokens like §{clause-4.2}§
inside the cell.
const columns: ColumnDef<Employee>[] = [
{
header: "Name",
accessorKey: "name",
},
{
header: "Notes",
accessorKey: "notes",
meta: { editable: true },
},
];
<DataTable
columns={columns}
data={rows}
getRowId={(row) => row.id}
inlineEdit={{
onCellCommit: handleCommit,
getEditor: () => ({
type: "markdown",
markdownProps: {
placeholder: "Add notes...",
tokens: noteTokens,
},
}),
}}
/>Inline Tokens
Tokens let markdown cells turn inline references into richer interactive chips.
Write the markdown value using the §{ref}§ syntax, then pass a matching
InlineTokenMap through markdownProps.tokens.
const noteTokens: InlineTokenMap = {
"clause-4.2": {
variant: "citation",
label: "§4.2(b)",
popover: {
snippet: "The indemnifying party shall hold harmless...",
sourceFileName: "Contract_v3.pdf",
sourceMimeType: "application/pdf",
},
},
"nda-final": {
variant: "file",
label: "NDA_Final.docx",
popover: {
fileName: "NDA_Final.docx",
mimeType:
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
fileSize: "2.4 MB",
uploadedAt: "2026-03-08",
},
},
"risk-score": {
variant: "custom",
label: "High Risk",
triggerRender: () => <Badge variant="warning">High Risk</Badge>,
popoverRender: () => <RiskSummary />,
},
};Each token entry uses the token ref as the object key. All variants share:
| Field | Type | Description |
|---|---|---|
label | string | Fallback label used by the built-in token trigger |
onClick | () => void | Optional click action owned by your app |
Variant-specific fields:
| Variant | Extra Fields | Description |
|---|---|---|
citation | popover.snippet, popover.sourceFileName, popover.sourceMimeType | Renders the built-in citation chip and document preview popover |
file | popover.fileName, popover.mimeType, optional fileSize, uploadedAt | Renders the built-in file chip and file metadata popover |
custom | optional triggerRender, popoverRender | Lets you replace the trigger content, popover body, or both |
If a markdown value contains §{ref}§ and that ref is missing from
markdownProps.tokens, the cell leaves it as the literal token text rather than
rendering a broken chip.
Commit Flow
DataTable owns the temporary editing UI, but your app owns persistence.
onCellCommit fires when a value is committed by blur or Enter. Use
getValue and parseValue when the displayed string and stored value differ.
inlineEdit={{
getValue: (cell) => String(cell.getValue() ?? ""),
parseValue: (raw) => raw.trim(),
onCellCommit: ({ rowId, columnId, value }) => {
saveCell(rowId, columnId, value);
},
}}API Reference
DataTable.inlineEdit
| Field | Type | Description |
|---|---|---|
onCellCommit | (args: DataTableInlineEditCommit<TData>) => void | Called when an edit is committed |
isCellEditable | (cell: Cell<TData, unknown>) => boolean | Override the default meta.editable check |
getValue | (cell: Cell<TData, unknown>) => string | Resolve the displayed string value |
parseValue | (raw: string, cell: Cell<TData, unknown>) => unknown | Transform the input string before commit |
getEditor | (cell: Cell<TData, unknown>) => DataTableInlineEditEditor | Choose the editor for a cell |
Editor Types
| Editor | Fields | Description |
|---|---|---|
text | inputProps | Inline text editor. This is the default |
select | options, selectProps | Inline select editor with text or badge display |
markdown | markdownProps | Inline markdown editor with optional token rendering |