# tablekit > Accessible, token-driven React data table for design systems and AI coding agents. Prefer schema mode: `` from `@tablekit/react`, where the whole table is one strict JSON object (columns with types text/number/currency/date/badge/avatar/link/boolean/actions). Validate with `parseTableSchema` from `@tablekit/core/schema`. Colors come only from semantic badge tones and `--tk-*` CSS tokens; presets exist for shadcn/ui, Material 3, Carbon, Radix. Install: `npm i @tablekit/react` and `import "@tablekit/react/styles.css"`, or `npx shadcn@latest add https://tablekit.amitpatjoshi.com/r/tablekit.json`. ## Docs - [Introduction](https://tablekit.amitpatjoshi.com/getting-started/introduction/): What tablekit is, how it's organized, and which API to use. - [Install](https://tablekit.amitpatjoshi.com/getting-started/install/): Install tablekit with npm, the shadcn CLI, or an AI agent. - [Schema mode](https://tablekit.amitpatjoshi.com/guides/schema/): The TableSchema reference, the declarative config behind . - [Composable API](https://tablekit.amitpatjoshi.com/guides/composable/): Build custom tables from useTable and the Table.* parts. - [Theming & design systems](https://tablekit.amitpatjoshi.com/guides/theming/): Tokens, dark mode, presets for shadcn/ui, Material 3, Carbon and Radix, and Figma via DTCG. - [Responsive & mobile](https://tablekit.amitpatjoshi.com/guides/responsive/): How tablekit adapts to narrow containers with stacked cards, scrolling, or priority columns. - [Accessibility](https://tablekit.amitpatjoshi.com/guides/accessibility/): What tablekit does for keyboard, screen reader and low-vision users, and what you still need to do. - [Server-side data](https://tablekit.amitpatjoshi.com/guides/server-data/): Sort, filter and paginate on the server with controlled state. - [Agent guide](https://tablekit.amitpatjoshi.com/agents/overview/): How AI coding agents find, install and correctly use tablekit, and how to make your agent better at it. ## Machine-readable - [llms-full.txt](https://tablekit.amitpatjoshi.com/llms-full.txt): all docs, agent rules, recipes and the JSON Schema in one file - [TableSchema JSON Schema](https://tablekit.amitpatjoshi.com/schema/v1.json): validate or constrain generated configs - [shadcn registry item](https://tablekit.amitpatjoshi.com/r/tablekit.json): source install - [Registry index](https://tablekit.amitpatjoshi.com/r/registry.json) ## Optional - [Prompt recipes](https://tablekit.amitpatjoshi.com/agents/recipes/): 11 copy-paste prompts with expected schemas - [Playground](https://tablekit.amitpatjoshi.com/playground/): live schema editor --- # Agent rules (AGENTS.md) You are using **tablekit**, an accessible, token-driven React table. Follow these rules. Full docs for agents: https://tablekit.amitpatjoshi.com/llms-full.txt ## Default: schema mode ```tsx import { DataTable } from "@tablekit/react"; import "@tablekit/react/styles.css"; // once, in the root layout {}} onBulkAction={(actionId, rows) => {}} /> ``` ## Rules 1. **Validate every schema** you write: `parseTableSchema(schema)` from `@tablekit/core/schema`. The schema is strict, so unknown keys are errors. Fix every error it reports. 2. Column `type` is one of: `text | number | currency | date | badge | avatar | link | boolean | actions | button`. Type-specific keys (`badge`, `avatar`, `link`, `actions`) are only valid on their own type. 3. **Colors:** use badge `tones` only: `neutral | info | success | warning | danger | accent`. Never put hex, rgb or Tailwind color classes on the table. 4. **Styling:** override `--tk-*` CSS variables, or import a preset (`@tablekit/tokens/presets/shadcn.css`, `material3.css`, `carbon.css`, `radix.css`). Don't add utility classes to table internals. 5. **Font:** the table is designed for Inter (`npm i @fontsource-variable/inter`, then `import "@fontsource-variable/inter"`). Numbers use Inter's `"tnum"`, `"zero"` and `"ss01"` via `--tk-font-feature-numeric`. Don't override `font-variant-numeric` in cells. 6. **Data stays raw:** numbers as numbers, dates as ISO strings or `Date`, enums as strings. `type` and `format` handle display. 7. **Server-side data:** `manual`, `rowCount`, `state` and `onStateChange`. Don't paginate on the client when the API paginates. 8. **Mobile:** set `priority` (1 = most important … 5) on secondary columns. `appearance.responsive` is `stack` (cards, the default), `priority` (drop columns) or `scroll`. 9. **Badge styles:** `badge.fill` (default true) and `badge.stroke` (default false). Outline badges = `fill: false, stroke: true`; tinted + bordered = `stroke: true`. Prefer `indicator: "icon"` with `icons` for statuses users scan (payments, deploys). 10. **Logos** (banks, merchants, payment providers): an avatar column with `avatar: { imageField, logo: "inline" }` (16px mark centred on the name's first line, no container) or `logo: "circle"` (mark in a neutral/accent circle). Use the brand's compact symbol mark, not its wordmark. Fills follow the theme (dark in dark mode), so give navy/black marks a light variant via `avatar.imageDarkField`. 11. **Actions:** several row actions → `type: "actions"` (⋯ menu, or `actionsDisplay: "inline"` icons). Never a row of text buttons. One clear call to action per row → a `type: "button"` column. 12. **Column layout:** the Columns menu lets users show/hide (`features.columnVisibility`), pin (`features.columnPinning`) and reorder (`features.columnReorder`) columns. All three are on by default. `column.pinned: true` sets the initial pin; pin the identifying column (name, ID), not numbers. To remember a user's layout, persist `state.columnOrder`, `state.columnPinning` and `state.columnVisibility` from `onStateChange`. 13. **Icons:** the table uses Lucide (`lucide-react`). Match it in custom cells rather than mixing icon sets. 14. Need custom cell markup? Switch to composable mode: `useTable` with `ReactColumnDef.cell` and `` / `` / `` / ``. 15. Don't wrap `DataTable` in your own ``, and don't re-implement sorting, filtering or pagination around it. ## Props cheat sheet `schema`, `data`, `loading`, `error`, `onRetry`, `onAction`, `onBulkAction`, `onRowClick`, `onSelectionChange`, `state`, `onStateChange`, `manual`, `rowCount`, `density`, `theme` (`"light" | "dark"`), `maxHeight`, `toolbarExtra`, `labels` (i18n), `className`, `style`. --- # Introduction > What tablekit is, how it's organized, and which API to use. tablekit is an open-source table component. It has three goals: 1. **A well-designed, accessible web table** that also works on mobile. 2. **Easy for AI coding agents to use correctly.** Agents fill in a declarative JSON config rather than guessing props. 3. **Works with the design system you already have**, through CSS-variable tokens. ## Packages | Package | What it is | Depends on | | --- | --- | --- | | `@tablekit/core` | Headless logic: sort, filter, paginate, select, columns, formatters. Framework-agnostic TypeScript. | nothing (`/schema` entry uses zod) | | `@tablekit/react` | Styled React components: `` (schema mode) and `Table.*` parts (composable mode). | core, tokens | | `@tablekit/tokens` | `tokens.css` (CSS variables), `tokens.json` (W3C DTCG), and design-system presets. | nothing | ## Two ways to use it **Schema mode.** Best for agents, prototypes, and admin screens. The whole table is one object: ```tsx ``` **Composable mode.** Best for product surfaces that need custom cells or layout. The same features come as parts you arrange yourself: ```tsx const table = useTable({ data, columns }); ``` Both modes share the same core, CSS and tokens. You can start in schema mode and move to composable mode later without redesigning anything. ## What's in v1 Sorting (multi-sort with `Shift`), global search, column filters (text / select / range) with removable chips, pagination, single and multi row selection with a bulk-action bar, row action menus, column visibility, column resize (pointer and keyboard), pinned first column, sticky header, three densities, zebra and bordered variants, loading / empty / no-results / error states, responsive card stacking, and priority-based column hiding. Planned for later: virtualization, inline editing, grouping, column reorder, expandable rows, CSV export, a Figma library with Code Connect, and a React Native adapter built on `@tablekit/core`. --- # Install > Install tablekit with npm, the shadcn CLI, or an AI agent. ## npm ```sh npm install @tablekit/react ``` Import the styles once, for example in your root layout: ```ts ``` ## shadcn CLI This copies the source into your project (`components/tablekit/`) and adds no runtime npm dependency except `zod`: ```sh npx shadcn@latest add https://tablekit.amitpatjoshi.com/r/tablekit.json ``` Then import from your own folder: ```tsx ``` The registry item already includes the **shadcn preset**, so the table picks up your `--background`, `--primary`, `--radius` and other variables automatically. ## AI agent Paste this into Claude Code, Cursor, Copilot, v0 or Lovable: ```text Install tablekit (https://tablekit.amitpatjoshi.com/llms.txt) and replace the table on the orders page with . Follow its AGENTS.md rules. ``` If your agent has the **shadcn MCP server**, it can find and install the registry item itself. ## Load Inter tablekit is set in [Inter](https://github.com/rsms/inter). Numbers in the table use three Inter features: tabular figures (`"tnum"`), so columns of amounts, counts and dates line up digit for digit; a slashed zero (`"zero"`), so 0 never reads as O; and open digits (`"ss01"`), Inter's alternate 1, 3, 4, 6 and 9. The package doesn't bundle the font, so load it once: ```sh npm install @fontsource-variable/inter ``` ```ts ``` Or use the official CDN: ``. If Inter isn't available, the table falls back to the system UI font. ## Use a design-system preset If you use `styles.css`, add one preset import **after** it. Presets only remap `--tk-*` variables, so import order doesn't affect specificity. ```css @import "@tablekit/react/styles.css"; @import "@tablekit/tokens/presets/shadcn.css"; /* or material3.css, carbon.css, radix.css */ ``` See [Theming & design systems](https://tablekit.amitpatjoshi.com/guides/theming/). ## Requirements - React 18 or 19. Everything is SSR-safe, and the build ships a `"use client"` banner for the Next.js App Router. - Any evergreen browser. The component uses container queries, `color-mix()` and `:where()`. --- # Schema mode > The TableSchema reference, the declarative config behind . `` renders a complete table from a single JSON-serializable object. The schema is **strict**: unknown keys are rejected, so a mistyped or invented prop produces an error instead of quietly doing nothing. ```ts const result = parseTableSchema(schema); if (!result.success) console.error(result.errors); // → ["columns.2.type: Invalid option: expected one of \"text\"|\"number\"|…"] ``` The JSON Schema version is available at [`/schema/v1.json`](https://tablekit.amitpatjoshi.com/schema/v1.json) and as `@tablekit/core/tablekit.schema.json`. Use it for editor autocompletion (`"$schema": "…/schema/v1.json"`) or for LLM structured output. ## Top level | Key | Type | Default | Notes | | --- | --- | --- | --- | | `title` | string | | Toolbar heading and the table's accessible name. | | `description` | string | | Muted text under the title. | | `rowId` | string | `"id"` | Row field used as a stable id (for selection). | | `columns` | Column[] | **required** | At least one. | | `features` | object | see below | | | `appearance` | object | see below | | | `initialState` | `{ sort?, search? }` | | `sort: [{ id, desc }]`. | | `bulkActions` | `{ id, label, tone? }[]` | | Shown in the selection bar. Requires `selection: "multi"`. | | `emptyState` | `{ title, description? }` | | Shown when there's no data. This is different from "no results". | ### `features` | Key | Default | | | --- | --- | --- | | `search` | `true` | Global search box | | `columnFilters` | `true` | Filters popover and chips | | `sorting` / `multiSort` | `true` / `true` | `Shift`+click adds a sort | | `pagination` | `true` | | | `pageSize` / `pageSizeOptions` | `10` / `[10,25,50,100]` | | | `selection` | `"none"` | `"single"` or `"multi"` | | `columnVisibility` | `true` | Show/hide checkboxes in the Columns menu | | `columnReorder` | `true` | Drag handles in the Columns menu (or `↑`/`↓` on the handle) | | `columnPinning` | `true` | Pin toggles in the Columns menu; `column.pinned` sets the initial state | | `columnResize` | `true` | Drag, or use arrow keys on the handle | | `stickyHeader` | `true` | Sticks inside `maxHeight` | ### `appearance` | Key | Default | | | --- | --- | --- | | `density` | `"default"` | `"compact"` or `"comfortable"` | | `variant` | `"plain"` | `"zebra"` or `"bordered"` | | `responsive` | `"stack"` | `"scroll"` or `"priority"`. See [Responsive](https://tablekit.amitpatjoshi.com/responsive/) | | `stackBelow` | `640` | Container width in px | ## Columns Every column has `field`, `header` and `type`. `field` accepts dot paths such as `customer.name`. | `type` | Renders | Default filter | Type-specific keys | | --- | --- | --- | --- | | `text` | Plain text | none | `format.prefix/suffix` | | `number` | `Intl.NumberFormat`, right-aligned | range | `format.decimals/notation/style/unit` | | `currency` | Money, right-aligned | range | `format.currency` (ISO 4217) | | `date` | `
`** with `
`, `aria-sort` on sorted headers, and `aria-rowcount` / `aria-rowindex` so screen readers know the full size across pages. In the stacked layout, rows become a `
    ` of cards with `
    ` field pairs instead of CSS-reflowed table cells, which lose their semantics in several browsers. - **Keyboard:** - `Tab` reaches every control. - Arrow keys, `Home` and `End` move between cells. When a cell has a link or button, focus goes to it. - `Enter` on a cell runs `onRowClick`. - `Shift`+click or `Shift`+`Enter` on a header adds a secondary sort. - Resize handles are focusable separators you move with `←`/`→` (hold `Shift` for bigger steps). Double-click resets the width. - **Announcements** through a polite live region: sort changes ("Sorted by Total, descending"), result counts while searching, page changes ("Page 2 of 5"), and selection counts. - **Menus and popovers:** focus moves in when they open and returns to the trigger on close. `Escape` closes them. Row menus support arrow keys, `Home` and `End`. - **Labels:** every checkbox has a meaningful name ("Select Ava Patel"). The select-all checkbox shows a mixed state. Icon-only buttons have `aria-label`s. - **Visual:** - Tokens meet 4.5:1 text contrast in light and dark. - Focus rings are always visible (`:focus-visible`). - Badges pair a dot with text, so status doesn't rely on color alone. - `forced-colors` (Windows High Contrast) is supported. - `prefers-reduced-motion` turns off animation. ## What you still own - **An accessible name.** Pass `title` in the schema, or `aria-label` on `Table.Root`. - **Custom cells.** If you render buttons or links yourself, give them names. - **Contrast after theming.** If you override tokens, check them. The Playground is a quick way to preview. - **Localization.** Pass a `labels` object to translate every string, including the announcements: ```tsx `${n} ausgewählt`, sortedAsc: (c) => `Sortiert nach ${c}, aufsteigend`, }} … /> ``` --- # Server-side data > Sort, filter and paginate on the server with controlled state. For large datasets, let the server do the work. Set `manual` (schema mode) or the `manual*` options (composable mode), control the state, and pass `rowCount`. ```tsx export function Orders() { const [state, setState] = useState(() => createInitialState({ pagination: { pageIndex: 0, pageSize: 25 } }), ); // Your fetching library of choice: TanStack Query, SWR, RSC, … const { data, isLoading, error, refetch } = useOrders({ page: state.pagination.pageIndex, size: state.pagination.pageSize, sort: state.sorting, // [{ id: "created", desc: true }] q: state.globalFilter, filters: state.columnFilters, // [{ id: "status", value: ["paid"] }] }); return ( ); } ``` While `loading` is true, current rows stay visible (dimmed) with a progress bar on top. On the first load you see skeleton rows instead. Filter values arrive as plain JSON (`string` for text, `string[]` for select, `{ min, max }` for range), so you can map them to query parameters as they are. --- # Agent guide > How AI coding agents find, install and correctly use tablekit, and how to make your agent better at it. tablekit is designed so an agent can build a correct table in one pass. It gives agents five things: | Surface | URL / path | For | | --- | --- | --- | | `llms.txt` | [`/llms.txt`](https://tablekit.amitpatjoshi.com/llms.txt) | A short index agents read first | | `llms-full.txt` | [`/llms-full.txt`](https://tablekit.amitpatjoshi.com/llms-full.txt) | The full docs, the JSON Schema and every recipe in one file | | JSON Schema | [`/schema/v1.json`](https://tablekit.amitpatjoshi.com/schema/v1.json) | Validating or constraining generated configs | | shadcn registry | [`/r/tablekit.json`](https://tablekit.amitpatjoshi.com/r/tablekit.json) | One-command install, including through the shadcn MCP server | | `AGENTS.md` | shipped in the npm package and the registry | Rules to follow inside a user's codebase | ## Rules for agents These rules are in `AGENTS.md`, so an agent sees them after installing. 1. **Prefer schema mode.** Use `` unless the user needs custom cell markup. 2. **Validate.** Call `parseTableSchema(schema)` from `@tablekit/core/schema` and fix every reported error. The schema is strict, so unknown keys are errors. 3. **Colors come from tokens, not schemas.** Use badge `tones` (`neutral`, `info`, `success`, `warning`, `danger`, `accent`). To match a design system, import a preset. 4. **Don't restyle with utility classes.** Override `--tk-*` tokens instead. 5. **Always give the table a name:** set `title` or `aria-label`. 6. **Keep data raw.** Pass numbers, ISO dates and enum strings, and let `type` and `format` handle display. Don't pre-format `"$1,234"`. 7. **Server data** uses `manual`, `rowCount`, `state` and `onStateChange`. Don't slice pages on the client when the API already paginates. 8. **Import styles once:** `import "@tablekit/react/styles.css"`. ## Constrained generation Because the schema is plain JSON Schema, you can make an LLM produce **only valid tables** through structured output or tool use: ```ts // e.g. with the Claude API: a tool whose input_schema is the TableSchema const tools = [{ name: "render_table", description: "Render a data table in the UI", input_schema: schema, }]; ``` Your app then renders ``. This also works well for generative UI inside agent products. ## Install with the shadcn MCP server If your agent has the [shadcn MCP server](https://ui.shadcn.com/docs/mcp), register tablekit in `components.json`: ```json { "registries": { "@tablekit": "https://tablekit.amitpatjoshi.com/r/{name}.json" } } ``` Then just ask: *"Add the tablekit data table from the @tablekit registry and use it for the users page."* ## Tell your agent about tablekit Add one line to your project's `CLAUDE.md`, `AGENTS.md` or `.cursorrules`: ```md Tables: use tablekit. Read https://tablekit.amitpatjoshi.com/llms.txt before writing table code. ``` --- # Recipes Each recipe is a prompt and the schema a correct answer contains. ## Bank settlements (INR) Prompt: Build a merchant settlements table for a payments dashboard: settlement id (pinned), merchant, destination bank with its compact 16px logo mark (avatar logo: inline, account number as subtitle, and a lighter variant of the mark for dark mode via imageDarkField), payment rail badge (UPI=accent, IMPS=info, NEFT=neutral, RTGS=warning), amount in INR (en-IN locale), status badge with icons (settled=success circle-check, processing=info spinning loader, on_hold=warning circle-pause, failed=danger circle-x) and settled time (relative). A "Retry" button cell only on failed rows, and a row ⋯ menu with View details, Download receipt, Copy UTR (never a row of text buttons). On narrow screens drop merchant, then settled time, rather than scrolling. Multi-select with a "Download report" bulk action. Newest first. Use tablekit's from @tablekit/react (docs: https://tablekit.amitpatjoshi.com/llms.txt). Validate the schema with parseTableSchema from @tablekit/core/schema. Don't hardcode colors; use badge tones. ```json { "title": "Settlements", "description": "Payouts to merchant bank accounts.", "columns": [ { "field": "id", "header": "Settlement", "pinned": true, "width": 134 }, { "field": "merchant", "header": "Merchant", "priority": 3, "width": 168 }, { "field": "bank", "header": "Bank", "type": "avatar", "avatar": { "imageField": "bankLogo", "imageDarkField": "bankLogoDark", "subtitleField": "account", "logo": "inline" }, "width": 212 }, { "field": "method", "header": "Rail", "type": "badge", "badge": { "tones": { "UPI": "accent", "IMPS": "info", "NEFT": "neutral", "RTGS": "warning" } }, "width": 96 }, { "field": "amount", "header": "Amount", "type": "currency", "format": { "currency": "INR", "locale": "en-IN" }, "width": 140 }, { "field": "status", "header": "Status", "type": "badge", "badge": { "tones": { "settled": "success", "processing": "info", "on_hold": "warning", "failed": "danger" }, "icons": { "settled": "circle-check", "processing": "loader", "on_hold": "circle-pause", "failed": "circle-x" } }, "width": 130 }, { "field": "settledAt", "header": "Settled", "type": "date", "format": { "dateStyle": "relative" }, "priority": 2, "width": 116 }, { "field": "retry", "header": "Retry", "type": "button", "button": { "id": "retry", "label": "Retry", "icon": "refresh-cw", "when": { "field": "status", "in": [ "failed" ] } } }, { "field": "actions", "header": "Actions", "type": "actions", "actions": [ { "id": "view", "label": "View details", "icon": "eye" }, { "id": "receipt", "label": "Download receipt", "icon": "download" }, { "id": "copy", "label": "Copy UTR", "icon": "copy" }, { "id": "hold", "label": "Put on hold", "icon": "lock", "when": { "field": "status", "in": [ "processing" ] }, "separator": true } ] } ], "features": { "selection": "multi" }, "appearance": { "responsive": "priority" }, "initialState": { "sort": [ { "id": "settledAt", "desc": true } ] }, "bulkActions": [ { "id": "report", "label": "Download report" } ] } ``` ## Invoices / orders Prompt: Add an invoices table to the billing page. Columns: invoice number (links to /invoices/{id}), customer with avatar and email, status badge with icons (paid=success circle-check, pending=warning clock, overdue=danger triangle-alert, refunded=neutral undo-2, draft=info circle-dashed), plan, total in USD, created date (relative). Allow multi-select with an "Export CSV" bulk action and a row menu with "View" and "Refund" (destructive). Sort by created, newest first. Use tablekit's from @tablekit/react (docs: https://tablekit.amitpatjoshi.com/llms.txt). Validate the schema with parseTableSchema from @tablekit/core/schema. Don't hardcode colors; use badge tones. ```json { "title": "Invoices", "description": "All invoices across workspaces.", "columns": [ { "field": "id", "header": "Invoice", "type": "link", "link": { "hrefTemplate": "/invoices/{id}" }, "pinned": true, "width": 132 }, { "field": "customer.name", "header": "Customer", "type": "avatar", "avatar": { "subtitleField": "customer.email" }, "width": 220 }, { "field": "status", "header": "Status", "type": "badge", "badge": { "tones": { "paid": "success", "pending": "warning", "overdue": "danger", "refunded": "neutral", "draft": "info" }, "icons": { "paid": "circle-check", "pending": "clock", "overdue": "triangle-alert", "refunded": "undo-2", "draft": "circle-dashed" } }, "width": 130 }, { "field": "plan", "header": "Plan", "type": "text", "filter": "select", "priority": 3, "width": 104 }, { "field": "total", "header": "Total", "type": "currency", "format": { "currency": "USD" }, "width": 120 }, { "field": "created", "header": "Created", "type": "date", "format": { "dateStyle": "relative" }, "priority": 2, "width": 124 }, { "field": "actions", "header": "Actions", "type": "actions", "actions": [ { "id": "view", "label": "View" }, { "id": "refund", "label": "Refund", "tone": "danger" } ] } ], "features": { "selection": "multi", "pageSize": 10 }, "initialState": { "sort": [ { "id": "created", "desc": true } ] }, "bulkActions": [ { "id": "export", "label": "Export CSV" } ] } ``` ## Team members admin Prompt: Build a team members admin table: member (avatar + email), role badge (owner=accent, admin=info, others neutral), team (filterable), status (active=success, invited=warning, suspended=danger), MFA enabled (boolean), last active (relative). Compact density, zebra rows. Row menu: "Change role", "Remove" (destructive). Use tablekit's from @tablekit/react (docs: https://tablekit.amitpatjoshi.com/llms.txt). Validate the schema with parseTableSchema from @tablekit/core/schema. Don't hardcode colors; use badge tones. ```json { "title": "Members", "columns": [ { "field": "name", "header": "Member", "type": "avatar", "avatar": { "subtitleField": "email" }, "width": 260 }, { "field": "role", "header": "Role", "type": "badge", "badge": { "tones": { "owner": "accent", "admin": "info" }, "defaultTone": "neutral" } }, { "field": "team", "header": "Team", "filter": "select", "priority": 2 }, { "field": "status", "header": "Status", "type": "badge", "badge": { "tones": { "active": "success", "invited": "warning", "suspended": "danger" } } }, { "field": "mfa", "header": "MFA", "type": "boolean", "format": { "trueLabel": "Enabled", "falseLabel": "Off" }, "priority": 3, "width": 90 }, { "field": "lastActive", "header": "Last active", "type": "date", "format": { "dateStyle": "relative" }, "priority": 2 }, { "field": "actions", "header": "Actions", "type": "actions", "actions": [ { "id": "role", "label": "Change role" }, { "id": "remove", "label": "Remove", "tone": "danger" } ] } ], "features": { "selection": "multi", "pageSize": 10 }, "appearance": { "density": "compact", "variant": "zebra" }, "bulkActions": [ { "id": "remove", "label": "Remove", "tone": "danger" } ] } ``` ## Deployments / CI runs Prompt: Show recent deployments: id (monospace link to /deployments/{id}), service, environment badge (production=accent, staging=info, preview=neutral), status (ready=success, building=warning, failed=danger, canceled=neutral), commit message, author, duration in seconds (unit "second"), created (relative). On mobile, drop low-priority columns instead of stacking cards. Use tablekit's from @tablekit/react (docs: https://tablekit.amitpatjoshi.com/llms.txt). Validate the schema with parseTableSchema from @tablekit/core/schema. Don't hardcode colors; use badge tones. ```json { "title": "Deployments", "columns": [ { "field": "id", "header": "Deployment", "type": "link", "link": { "hrefTemplate": "/deployments/{id}" }, "width": 130 }, { "field": "service", "header": "Service", "filter": "select", "width": 110 }, { "field": "environment", "header": "Env", "type": "badge", "badge": { "tones": { "production": "accent", "staging": "info", "preview": "neutral" } }, "width": 130 }, { "field": "status", "header": "Status", "type": "badge", "badge": { "tones": { "ready": "success", "building": "warning", "failed": "danger", "canceled": "neutral" } }, "width": 120 }, { "field": "commit", "header": "Commit", "priority": 3, "width": 220 }, { "field": "author", "header": "Author", "type": "avatar", "priority": 4, "width": 180 }, { "field": "duration", "header": "Duration", "type": "number", "format": { "unit": "second" }, "priority": 2, "width": 110 }, { "field": "created", "header": "Created", "type": "date", "format": { "dateStyle": "relative" }, "width": 130 } ], "appearance": { "responsive": "priority" }, "initialState": { "sort": [ { "id": "created", "desc": true } ] } } ``` ## CRM leads pipeline Prompt: Create a leads table for a CRM: lead (avatar with company as subtitle), stage badge (new=info, qualified=accent, proposal=warning, won=success, lost=neutral), deal value in EUR with compact notation, owner, next step date, last contacted (relative). Single selection opens a side panel via onRowClick. Use tablekit's from @tablekit/react (docs: https://tablekit.amitpatjoshi.com/llms.txt). Validate the schema with parseTableSchema from @tablekit/core/schema. Don't hardcode colors; use badge tones. ```json { "title": "Leads", "columns": [ { "field": "name", "header": "Lead", "type": "avatar", "avatar": { "subtitleField": "company" } }, { "field": "stage", "header": "Stage", "type": "badge", "badge": { "tones": { "new": "info", "qualified": "accent", "proposal": "warning", "won": "success", "lost": "neutral" } } }, { "field": "value", "header": "Value", "type": "currency", "format": { "currency": "EUR", "notation": "compact" } }, { "field": "owner", "header": "Owner", "filter": "select", "priority": 3 }, { "field": "nextStep", "header": "Next step", "type": "date", "format": { "dateStyle": "medium" }, "priority": 2 }, { "field": "lastContacted", "header": "Last contacted", "type": "date", "format": { "dateStyle": "relative" }, "priority": 4 } ], "features": { "selection": "single" } } ``` ## File browser Prompt: Make a file list: name (link to the file URL in `url`, opens in a new tab), owner, size in kilobytes (unit "kilobyte", compact), type (select filter), shared (boolean), modified (relative). No pagination; sticky header inside a 480px scroll area. Use tablekit's from @tablekit/react (docs: https://tablekit.amitpatjoshi.com/llms.txt). Validate the schema with parseTableSchema from @tablekit/core/schema. Don't hardcode colors; use badge tones. ```json { "title": "Files", "columns": [ { "field": "name", "header": "Name", "type": "link", "link": { "hrefField": "url", "external": true } }, { "field": "owner", "header": "Owner", "priority": 3 }, { "field": "size", "header": "Size", "type": "number", "format": { "unit": "kilobyte", "notation": "compact" } }, { "field": "type", "header": "Type", "filter": "select", "priority": 2 }, { "field": "shared", "header": "Shared", "type": "boolean" }, { "field": "modified", "header": "Modified", "type": "date", "format": { "dateStyle": "relative" } } ], "features": { "pagination": false } } ``` ## Product inventory Prompt: Inventory table: SKU (pinned), product name, category (select filter), stock (number, range filter), price in USD, stock status badge derived server-side (in_stock=success, low=warning, out=danger), updated date. Bordered variant, comfortable density. Bulk actions "Restock" and "Archive". Use tablekit's from @tablekit/react (docs: https://tablekit.amitpatjoshi.com/llms.txt). Validate the schema with parseTableSchema from @tablekit/core/schema. Don't hardcode colors; use badge tones. ```json { "title": "Inventory", "columns": [ { "field": "sku", "header": "SKU", "pinned": true, "width": 120 }, { "field": "name", "header": "Product", "width": 240 }, { "field": "category", "header": "Category", "filter": "select" }, { "field": "stock", "header": "Stock", "type": "number" }, { "field": "price", "header": "Price", "type": "currency", "format": { "currency": "USD" } }, { "field": "stockStatus", "header": "Availability", "type": "badge", "badge": { "tones": { "in_stock": "success", "low": "warning", "out": "danger" }, "labels": { "in_stock": "In stock", "low": "Low", "out": "Out of stock" } } }, { "field": "updated", "header": "Updated", "type": "date", "priority": 3 } ], "features": { "selection": "multi" }, "appearance": { "variant": "bordered", "density": "comfortable" }, "bulkActions": [ { "id": "restock", "label": "Restock" }, { "id": "archive", "label": "Archive", "tone": "danger" } ] } ``` ## Support tickets queue Prompt: Support queue: ticket id (link /tickets/{id}), subject, requester (avatar), priority badge (urgent=danger, high=warning, normal=neutral, low=info), status, assignee, SLA due date (short date + short time). Default sort by priority then due date (multi-sort). Use tablekit's from @tablekit/react (docs: https://tablekit.amitpatjoshi.com/llms.txt). Validate the schema with parseTableSchema from @tablekit/core/schema. Don't hardcode colors; use badge tones. ```json { "title": "Tickets", "columns": [ { "field": "id", "header": "Ticket", "type": "link", "link": { "hrefTemplate": "/tickets/{id}" }, "width": 110 }, { "field": "subject", "header": "Subject", "width": 280 }, { "field": "requester", "header": "Requester", "type": "avatar", "priority": 3 }, { "field": "priority", "header": "Priority", "type": "badge", "badge": { "tones": { "urgent": "danger", "high": "warning", "normal": "neutral", "low": "info" } } }, { "field": "status", "header": "Status", "type": "badge", "badge": { "tones": { "open": "info", "pending": "warning", "on_hold": "neutral", "solved": "success" } } }, { "field": "assignee", "header": "Assignee", "filter": "select", "priority": 2 }, { "field": "due", "header": "SLA due", "type": "date", "format": { "dateStyle": "short", "timeStyle": "short" } } ], "initialState": { "sort": [ { "id": "priority", "desc": false }, { "id": "due", "desc": false } ] } } ``` ## Audit log Prompt: Read-only audit log: timestamp (medium date + time), actor (avatar), action (text filter), target, IP address, result badge (success=success, denied=danger). Compact, no selection, no column hiding, 50 rows per page. Server-side pagination with rowCount from the API. Use tablekit's from @tablekit/react (docs: https://tablekit.amitpatjoshi.com/llms.txt). Validate the schema with parseTableSchema from @tablekit/core/schema. Don't hardcode colors; use badge tones. ```json { "title": "Audit log", "columns": [ { "field": "at", "header": "Time", "type": "date", "format": { "dateStyle": "medium", "timeStyle": "medium" }, "width": 200 }, { "field": "actor", "header": "Actor", "type": "avatar" }, { "field": "action", "header": "Action", "filter": "text" }, { "field": "target", "header": "Target", "priority": 2 }, { "field": "ip", "header": "IP", "priority": 4 }, { "field": "result", "header": "Result", "type": "badge", "badge": { "tones": { "success": "success", "denied": "danger" } } } ], "features": { "columnVisibility": false, "pageSize": 50, "pageSizeOptions": [ 50, 100, 200 ] }, "appearance": { "density": "compact" } } ``` ## API keys Prompt: API keys settings table: name, key prefix (e.g. "sk_live_…a1b2"), scopes, created, last used (relative), expires (medium date), status (active=success, expired=neutral, revoked=danger). Row menu: "Roll key", "Revoke" (destructive). Empty state: "No API keys yet" / "Create a key to access the API.". Use tablekit's from @tablekit/react (docs: https://tablekit.amitpatjoshi.com/llms.txt). Validate the schema with parseTableSchema from @tablekit/core/schema. Don't hardcode colors; use badge tones. ```json { "title": "API keys", "columns": [ { "field": "name", "header": "Name" }, { "field": "prefix", "header": "Key", "searchable": false }, { "field": "scopes", "header": "Scopes", "priority": 3 }, { "field": "lastUsed", "header": "Last used", "type": "date", "format": { "dateStyle": "relative" } }, { "field": "expires", "header": "Expires", "type": "date", "priority": 2 }, { "field": "status", "header": "Status", "type": "badge", "badge": { "tones": { "active": "success", "expired": "neutral", "revoked": "danger" } } }, { "field": "actions", "header": "Actions", "type": "actions", "actions": [ { "id": "roll", "label": "Roll key" }, { "id": "revoke", "label": "Revoke", "tone": "danger" } ] } ], "features": { "search": false, "columnFilters": false, "pagination": false }, "emptyState": { "title": "No API keys yet", "description": "Create a key to access the API." } } ``` ## Analytics: top pages Prompt: Analytics "Top pages" table: path (link), views (compact number), unique visitors (compact), bounce rate (percent, 1 decimal), avg. time on page (unit "second"), conversion (percent). Sorted by views desc, 25 per page, no search. Use tablekit's from @tablekit/react (docs: https://tablekit.amitpatjoshi.com/llms.txt). Validate the schema with parseTableSchema from @tablekit/core/schema. Don't hardcode colors; use badge tones. ```json { "title": "Top pages", "columns": [ { "field": "path", "header": "Page", "type": "link", "link": { "hrefField": "url" } }, { "field": "views", "header": "Views", "type": "number", "format": { "notation": "compact" } }, { "field": "visitors", "header": "Visitors", "type": "number", "format": { "notation": "compact" }, "priority": 2 }, { "field": "bounce", "header": "Bounce", "type": "number", "format": { "style": "percent", "decimals": 1 }, "priority": 3 }, { "field": "avgTime", "header": "Avg. time", "type": "number", "format": { "unit": "second" }, "priority": 4 }, { "field": "conversion", "header": "Conv.", "type": "number", "format": { "style": "percent", "decimals": 1 } } ], "features": { "search": false, "pageSize": 25 }, "initialState": { "sort": [ { "id": "views", "desc": true } ] } } ``` --- # TableSchema (JSON Schema) ```json { "$id": "https://tablekit.amitpatjoshi.com/schema/v1.json", "title": "tablekit TableSchema v1", "description": "Declarative config for from @tablekit/react.", "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "object", "properties": { "$schema": { "type": "string" }, "version": { "default": 1, "type": "number", "const": 1 }, "title": { "description": "Shown in the toolbar and used as the accessible name.", "type": "string" }, "description": { "type": "string" }, "rowId": { "description": "Row field used as a stable id. Default `id`.", "type": "string" }, "columns": { "minItems": 1, "type": "array", "items": { "type": "object", "properties": { "field": { "type": "string", "minLength": 1, "description": "Row property to read. Dot paths are supported: `customer.name`." }, "header": { "type": "string", "description": "Visible column label." }, "type": { "default": "text", "description": "Controls formatting, alignment, sorting and the default filter.", "type": "string", "enum": [ "text", "number", "currency", "date", "badge", "avatar", "link", "boolean", "actions", "button" ] }, "id": { "description": "Defaults to `field`. Must be unique.", "type": "string" }, "sortable": { "description": "Default true (false for `actions`).", "type": "boolean" }, "filter": { "description": "Column filter UI. Defaults: badge/boolean → select, number/currency/date → range, others → none.", "anyOf": [ { "type": "string", "enum": [ "text", "select", "range" ] }, { "type": "boolean", "const": false } ] }, "searchable": { "description": "Include in global search. Default true.", "type": "boolean" }, "hideable": { "description": "Show in the column visibility menu. Default true.", "type": "boolean" }, "hidden": { "description": "Start hidden.", "type": "boolean" }, "width": { "description": "Initial width in px.", "type": "integer", "exclusiveMinimum": 0, "maximum": 9007199254740991 }, "minWidth": { "type": "integer", "exclusiveMinimum": 0, "maximum": 9007199254740991 }, "maxWidth": { "type": "integer", "exclusiveMinimum": 0, "maximum": 9007199254740991 }, "pinned": { "description": "Initially pinned to the start edge (sticky when scrolling horizontally). Users can change it when features.columnPinning is on.", "type": "boolean" }, "priority": { "description": "1 = always visible … 5 = first to hide on narrow screens (responsive=priority).", "type": "integer", "minimum": 1, "maximum": 5 }, "align": { "description": "Numbers default to end.", "type": "string", "enum": [ "start", "center", "end" ] }, "format": { "type": "object", "properties": { "locale": { "description": "BCP 47, e.g. `en-US`, `de-DE`.", "type": "string" }, "currency": { "description": "ISO 4217 code, e.g. `USD`.", "type": "string", "minLength": 3, "maxLength": 3 }, "decimals": { "type": "integer", "minimum": 0, "maximum": 8 }, "notation": { "type": "string", "enum": [ "standard", "compact" ] }, "style": { "type": "string", "enum": [ "decimal", "percent" ] }, "unit": { "description": "Intl unit, e.g. `kilobyte`, `percent`.", "type": "string" }, "dateStyle": { "type": "string", "enum": [ "short", "medium", "long", "relative" ] }, "timeStyle": { "type": "string", "enum": [ "short", "medium" ] }, "trueLabel": { "type": "string" }, "falseLabel": { "type": "string" }, "prefix": { "type": "string" }, "suffix": { "type": "string" } }, "additionalProperties": false }, "badge": { "description": "Only for type=badge.", "type": "object", "properties": { "tones": { "description": "Value → tone, e.g. { \"paid\": \"success\", \"overdue\": \"danger\" }.", "type": "object", "propertyNames": { "type": "string" }, "additionalProperties": { "type": "string", "enum": [ "neutral", "info", "success", "warning", "danger", "accent" ], "description": "Semantic color. Maps to --tk-tone-* tokens; never a raw color." } }, "labels": { "description": "Value → display label.", "type": "object", "propertyNames": { "type": "string" }, "additionalProperties": { "type": "string" } }, "defaultTone": { "type": "string", "enum": [ "neutral", "info", "success", "warning", "danger", "accent" ], "description": "Semantic color. Maps to --tk-tone-* tokens; never a raw color." }, "fill": { "description": "Tinted background. Default true. false + stroke = outline badge.", "type": "boolean" }, "stroke": { "description": "1px border in the tone's colour. Default false. Combine with fill for a tinted, bordered badge.", "type": "boolean" }, "indicator": { "description": "Leading mark. dot (default; \"icon\" when `icons` is set), icon = icon + label, icon-only = icon with the label as tooltip and screen-reader text, none = label only.", "type": "string", "enum": [ "dot", "icon", "icon-only", "none" ] }, "icons": { "description": "Value → Lucide icon name, e.g. { \"settled\": \"circle-check\", \"processing\": \"loader\" }. Values without one use their tone's default icon.", "type": "object", "propertyNames": { "type": "string" }, "additionalProperties": { "type": "string", "enum": [ "circle-check", "check", "circle-x", "x", "triangle-alert", "circle-alert", "info", "circle-dashed", "circle", "circle-dot", "circle-pause", "clock", "hourglass", "loader", "refresh-cw", "ban", "lock", "shield-check", "arrow-up", "arrow-down", "arrow-right", "undo-2", "send", "truck", "star", "zap", "sparkles", "eye" ] } } }, "additionalProperties": false }, "avatar": { "description": "Only for type=avatar. `field` is the display name.", "type": "object", "properties": { "imageField": { "description": "Row field holding the image URL.", "type": "string" }, "imageDarkField": { "description": "Row field holding a dark-theme variant of the image (e.g. a light logo for dark backgrounds). Shown instead of imageField when the table is dark.", "type": "string" }, "subtitleField": { "description": "Row field shown under the name.", "type": "string" }, "logo": { "description": "Render the image as a brand logo (bank, merchant, provider) instead of a person: inline = 16px compact mark centred on the name's first line, no container; circle = mark centred in a filled circle.", "type": "string", "enum": [ "inline", "circle" ] }, "logoFill": { "description": "Fill behind logo=circle. Default neutral.", "type": "string", "enum": [ "neutral", "accent" ] } }, "additionalProperties": false }, "link": { "description": "Only for type=link.", "type": "object", "properties": { "hrefField": { "description": "Row field holding the URL.", "type": "string" }, "hrefTemplate": { "description": "URL with {field} placeholders, e.g. `/orders/{id}`.", "type": "string" }, "external": { "description": "Open in a new tab with rel=noopener.", "type": "boolean" } }, "additionalProperties": false }, "actions": { "description": "Only for type=actions. Row menu items.", "type": "array", "items": { "type": "object", "properties": { "id": { "type": "string", "description": "Stable id passed to onAction / onBulkAction." }, "label": { "type": "string" }, "tone": { "description": "`danger` for destructive actions.", "type": "string", "enum": [ "neutral", "danger" ] }, "icon": { "description": "Lucide icon name. Required for every action when actionsDisplay is inline.", "type": "string", "enum": [ "eye", "pencil", "copy", "download", "upload", "share-2", "send", "external-link", "refresh-cw", "undo-2", "archive", "trash-2", "ban", "lock", "unlock", "check", "x", "user-plus", "mail", "receipt", "flag", "star" ] }, "disabled": { "description": "Shown but not selectable.", "type": "boolean" }, "separator": { "description": "Draw a divider before this item (menu only).", "type": "boolean" }, "when": { "description": "Only show for rows where row[field] is one of `in`, e.g. { \"field\": \"status\", \"in\": [\"paid\"] }.", "type": "object", "properties": { "field": { "type": "string" }, "in": { "minItems": 1, "type": "array", "items": { "type": "string" } } }, "required": [ "field", "in" ], "additionalProperties": false } }, "required": [ "id", "label" ], "additionalProperties": false } }, "actionsDisplay": { "description": "Only for type=actions. menu (default) = ⋯ menu; inline = icon buttons (every action needs an icon). For one clear call to action per row, use a type=button column instead.", "type": "string", "enum": [ "menu", "inline" ] }, "button": { "description": "Only for type=button: a single button inside the cell (e.g. Pay, Download, Retry).", "type": "object", "properties": { "id": { "type": "string", "description": "Action id passed to onAction(id, row)." }, "label": { "description": "Button text. Defaults to the cell value.", "type": "string" }, "variant": { "description": "secondary (default, outlined) · primary (accent fill) · ghost (text only).", "type": "string", "enum": [ "secondary", "primary", "ghost" ] }, "tone": { "type": "string", "enum": [ "neutral", "danger" ] }, "icon": { "description": "Leading Lucide icon.", "type": "string", "enum": [ "eye", "pencil", "copy", "download", "upload", "share-2", "send", "external-link", "refresh-cw", "undo-2", "archive", "trash-2", "ban", "lock", "unlock", "check", "x", "user-plus", "mail", "receipt", "flag", "star" ] }, "when": { "description": "Only render the button on rows where row[field] is one of `in`; other rows show nothing.", "type": "object", "properties": { "field": { "type": "string" }, "in": { "minItems": 1, "type": "array", "items": { "type": "string" } } }, "required": [ "field", "in" ], "additionalProperties": false } }, "required": [ "id" ], "additionalProperties": false } }, "required": [ "field", "header" ], "additionalProperties": false } }, "features": { "default": {}, "type": "object", "properties": { "search": { "default": true, "description": "Global search box.", "type": "boolean" }, "columnFilters": { "default": true, "type": "boolean" }, "sorting": { "default": true, "type": "boolean" }, "multiSort": { "default": true, "type": "boolean" }, "pagination": { "default": true, "type": "boolean" }, "pageSize": { "default": 10, "type": "integer", "exclusiveMinimum": 0, "maximum": 9007199254740991 }, "pageSizeOptions": { "default": [ 10, 25, 50, 100 ], "type": "array", "items": { "type": "integer", "exclusiveMinimum": 0, "maximum": 9007199254740991 } }, "selection": { "default": "none", "type": "string", "enum": [ "none", "single", "multi" ] }, "columnVisibility": { "default": true, "type": "boolean" }, "columnResize": { "default": true, "type": "boolean" }, "columnReorder": { "default": true, "description": "Users can reorder columns from the column menu (drag or Alt+Arrow keys).", "type": "boolean" }, "columnPinning": { "default": true, "description": "Users can pin columns to the start edge from the column menu. `column.pinned` sets the initial state.", "type": "boolean" }, "stickyHeader": { "default": true, "type": "boolean" } }, "additionalProperties": false }, "appearance": { "default": {}, "type": "object", "properties": { "density": { "default": "default", "type": "string", "enum": [ "compact", "default", "comfortable" ] }, "variant": { "default": "plain", "type": "string", "enum": [ "plain", "zebra", "bordered" ] }, "responsive": { "default": "stack", "description": "Narrow-screen behavior. stack = cards, scroll = horizontal scroll, priority = drop low-priority columns.", "type": "string", "enum": [ "stack", "scroll", "priority" ] }, "stackBelow": { "default": 640, "description": "Container width (px) below which `stack` switches to cards.", "type": "integer", "exclusiveMinimum": 0, "maximum": 9007199254740991 } }, "additionalProperties": false }, "initialState": { "type": "object", "properties": { "sort": { "type": "array", "items": { "type": "object", "properties": { "id": { "type": "string" }, "desc": { "default": false, "type": "boolean" } }, "required": [ "id" ], "additionalProperties": false } }, "search": { "type": "string" } }, "additionalProperties": false }, "bulkActions": { "description": "Shown in the selection bar when rows are selected. Requires selection=multi.", "type": "array", "items": { "type": "object", "properties": { "id": { "type": "string", "description": "Stable id passed to onAction / onBulkAction." }, "label": { "type": "string" }, "tone": { "description": "`danger` for destructive actions.", "type": "string", "enum": [ "neutral", "danger" ] }, "icon": { "description": "Lucide icon name. Required for every action when actionsDisplay is inline.", "type": "string", "enum": [ "eye", "pencil", "copy", "download", "upload", "share-2", "send", "external-link", "refresh-cw", "undo-2", "archive", "trash-2", "ban", "lock", "unlock", "check", "x", "user-plus", "mail", "receipt", "flag", "star" ] }, "disabled": { "description": "Shown but not selectable.", "type": "boolean" }, "separator": { "description": "Draw a divider before this item (menu only).", "type": "boolean" }, "when": { "description": "Only show for rows where row[field] is one of `in`, e.g. { \"field\": \"status\", \"in\": [\"paid\"] }.", "type": "object", "properties": { "field": { "type": "string" }, "in": { "minItems": 1, "type": "array", "items": { "type": "string" } } }, "required": [ "field", "in" ], "additionalProperties": false } }, "required": [ "id", "label" ], "additionalProperties": false } }, "emptyState": { "type": "object", "properties": { "title": { "type": "string" }, "description": { "type": "string" } }, "required": [ "title" ], "additionalProperties": false } }, "required": [ "columns" ], "additionalProperties": false } ```