Copy/Paste & Undo
Excel/Sheets-compatible TSV copy/paste, a single undo/redo history across edits, and bulk-clear.
Installation
Copy/paste and undo ship with Data Table — no separate install.
npx shadcn@latest add @kotsas-ui/data-tableThis feature confirms its results with toasts. The install adds components/ui/sonner.tsx for you, but the <Toaster /> it exports must be mounted once in your root layout — otherwise the table works but its feedback never appears.
import { Toaster } from "@/components/ui/sonner"
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>
{children}
<Toaster />
</body>
</html>
)
}Usage
Try it in the demo: edit a cell, copy a selection (Ctrl/Cmd+C), paste a block (Ctrl/Cmd+V), then undo (Ctrl/Cmd+Z) — a multi-cell paste or a selection-wide clear undoes as a single step, not one step per cell.
Set up CI pipeline | High | Done | $2,400.00 | True |
Design onboarding flow | Medium | In progress | $3,200.00 | False |
Migrate auth to OAuth2 | Urgent | In progress | $5,600.00 | False |
Write API docs | Low | To do | $900.00 | False |
Keyboard shortcuts
| Keys | Action |
|---|---|
| Ctrl/Cmd+Z | Undo the last edit, paste, or bulk-clear. |
| Ctrl/Cmd+Shift+Z | Redo. |
| Ctrl/Cmd+C | Copy the active cell, or every visible column of every selected row, as TSV. |
| Ctrl/Cmd+V | Paste a TSV block starting at the active cell; rows past the last one are reported via onCreateRows. |
| Delete / Backspace | Clear every editable column of the selected rows, or just the active cell if nothing is selected. |
Examples
Copy/paste serialization is per-column, populated automatically by defineColumns from each field's own toClipboard/fromClipboard. A raw ColumnDef can opt in the same way via meta:
col.currency("budget", { currency: "USD" })
// meta.toClipboard / meta.fromClipboard are populated automatically by
// defineColumns from the field's own serializers. A raw ColumnDef (the
// escape hatch, not built via col.*) can opt in the same way:
{
id: "custom",
meta: {
label: "Custom",
toClipboard: (v) => String(v ?? ""),
fromClipboard: (text) => text, // return undefined to reject a pasted value
},
}API Reference
| Prop | Type | Default | Description |
|---|---|---|---|
| editable? | boolean | false | Table-level default for whether cells can be edited; per-column editable in defineColumns overrides it. |
| onUpdateData? | (rowId, columnId, value) => void | — | Called for every committed edit (typing, paste, bulk-clear, undo, redo). The library never mutates data itself — you own applying the change. |
| onCreateRows? | (partialRows: Partial<TData>[]) => void | — | Called when a paste extends past the last row, once per overflow row, with only the pasted columns populated. |
| meta.toClipboard? | (value) => string | — | Per-column serializer for copy/export — populated automatically by defineColumns from the field type. |
| meta.fromClipboard? | (text) => unknown | — | Per-column parser for paste. Return undefined to reject a value; omit entirely to make a column paste-proof. |