

<Preview name="InlineEditDataTableExample" />

## Overview [#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-cells]

Text is the default editor for short string fields like emails, roles, or
locations.

<Tabs className="bg-transparent border-none rounded-none" items="['Preview', 'Code']">
  <Tab value="Preview" className="p-0">
    <Preview name="TextInlineEditDataTableExample" />
  </Tab>

  <Tab value="Code">
    ```tsx
    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);
        },
      }}
    />
    ```
  </Tab>
</Tabs>

## Select Cells [#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.

<Tabs className="bg-transparent border-none rounded-none" items="['Preview', 'Code']">
  <Tab value="Preview" className="p-0">
    <Preview name="SelectInlineEditDataTableExample" />
  </Tab>

  <Tab value="Code">
    ```tsx
    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" };
        },
      }}
    />
    ```
  </Tab>
</Tabs>

### Option Shape [#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.

```tsx
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 [#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.

<Tabs className="bg-transparent border-none rounded-none" items="['Preview', 'Code']">
  <Tab value="Preview" className="p-0">
    <Preview name="MarkdownInlineEditDataTableExample" />
  </Tab>

  <Tab value="Code">
    ```tsx
    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,
          },
        }),
      }}
    />
    ```
  </Tab>
</Tabs>

### Inline Tokens [#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`.

```tsx
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 [#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.

```tsx
inlineEdit={{
  getValue: (cell) => String(cell.getValue() ?? ""),
  parseValue: (raw) => raw.trim(),
  onCellCommit: ({ rowId, columnId, value }) => {
    saveCell(rowId, columnId, value);
  },
}}
```

## API Reference [#api-reference]

### `DataTable.inlineEdit` [#datatableinlineedit]

| 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-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 |
