# RFC: DataTable ## Status Proposed ## Summary `DataTable` is the next high-value advanced pattern for `@ai-ui/ui`, but it should not ship as a giant "kitchen sink" component. The recommended path is: 1. adopt a headless row-model dependency for the hard table state problems 2. keep the public API source-owned and design-system specific 3. ship a narrow core table first 4. layer richer behaviors only after the base contract stabilizes The recommended dependency choice is `@tanstack/react-table`, but only as an internal implementation detail. Consumers should build against `@ai-ui/ui`, not against TanStack APIs directly. ## Why now The repo has already completed most of the preconditions that the roadmap called out before advanced patterns: - token system is in place - core component layer is in place - Storybook coverage exists across the current primitives - interaction and smoke coverage are present - `Sheet` and `EmptyState` are now available as adjacent workflow patterns Relevant current building blocks already exist in `packages/ui`: - input and form controls: `Input`, `Select`, `Checkbox`, `RadioGroup`, `Switch`, `Form` - structure and feedback: `Card`, `Badge`, `Alert`, `Separator`, `Skeleton`, `Progress` - overflow and contextual actions: `DropdownMenu`, `Popover`, `Tooltip`, `Sheet`, `Dialog`, `Toast` - empty and first-run states: `EmptyState` That means the main missing piece for list-heavy product surfaces is not another primitive. It is a stable data presentation pattern. ## Problem statement Today the design system can express forms, overlays, command/search, and empty states, but it cannot yet express a reusable operational list surface such as: - release queues - approval backlogs - rollout logs - audit result lists - environment health tables - reviewer assignments Without a `DataTable` pattern, application code will drift into one-off table wrappers with inconsistent: - toolbar layout - filter/search interactions - row selection behavior - loading and empty states - keyboard behavior - sticky headers and scrolling decisions - bulk action affordances - pagination treatment This is exactly the kind of "parallel wrapper" problem the roadmap warns against. ## Non-goals The first implementation should **not** attempt to solve every table problem. Explicit non-goals for the first slice: - virtualization - column resizing - column reordering - nested tree rows - grouped/aggregated rows - drag and drop - inline cell editing - Excel-style spreadsheet behavior - server transport concerns - generic charting or pivot-table behavior Those can come later if real product surfaces prove they are needed. ## Current repo constraints This RFC is anchored in the current repo, not an idealized future state. ### Architectural constraints - Components are source-owned and exported from `packages/ui/src/index.ts` - Styling is token-first and stateful styling flows through stable `data-*` attributes - Slot naming is already standardized in `packages/ui/src/lib/contracts.ts` - Motion should be purposeful and reduced-motion safe - Stories are expected to explain anatomy and behavior, not just render a demo - QA already uses `Vitest`, Storybook interaction coverage, and Playwright smoke ### Dependency constraints Current `packages/ui/package.json` has no table model dependency, no date library, and no grid engine. That means the first `DataTable` implementation must either: - hand-roll row modeling, sorting, selection, and visibility management, or - adopt a focused headless dependency Hand-rolling is not recommended for this repo stage. ## Recommendation Use `@tanstack/react-table` internally for the first `DataTable` implementation. ### Why TanStack Table It solves the complex headless data problems we do not want to rediscover: - row modeling - sorting state - filtering state - pagination state - selection state - column visibility - stable cell/header modeling This matches the repo's principle of using a strong interaction/state base under a source-owned UI layer, the same way Radix underpins overlays and controls. ### Why not build it from scratch Building even a "simple" table pattern from scratch will immediately force the repo to own: - sorting semantics - accessor APIs - row identity rules - selection bookkeeping - controlled vs uncontrolled state design - column metadata normalization That is too much API design surface for a first table release. ### Why not expose TanStack directly Exposing TanStack types and concepts as the public API would weaken the repo's current direction: - it would leak a third-party mental model into consumer code - it would make docs look like a wrapper around someone else's library - it would make future refactors harder The public contract should stay `@ai-ui/ui` first. TanStack should remain an implementation detail wherever possible, with any unavoidable type exposure deliberately minimized. ## Proposed scope: Stage 1 core The first shipping slice should cover the operational table use case, not the spreadsheet use case. ### Must-have behaviors - typed columns - sortable columns - optional row selection - optional client-side text filter - empty state rendering - loading state rendering - pagination controls - column-level cell formatting - row-level actions slot ### Must-have supporting surfaces - toolbar for search, filters, and view actions - sticky or visually distinct header treatment - bulk selection affordance when selection is enabled - responsive overflow strategy ## Proposed public API shape The API should be compositional and stable, following the repo's current component contract. ### Root component ```tsx row.id} loading={loading} empty={} searchValue={search} onSearchValueChange={setSearch} selection={selection} onSelectionChange={setSelection} sorting={sorting} onSortingChange={setSorting} /> ``` ### Suggested slot family - `DataTable` - `DataTableToolbar` - `DataTableSearch` - `DataTableFilters` - `DataTableContent` - `DataTableTable` - `DataTableHeader` - `DataTableHeaderCell` - `DataTableBody` - `DataTableRow` - `DataTableCell` - `DataTableEmpty` - `DataTableLoading` - `DataTablePagination` - `DataTableSelectionBar` This is intentionally a pattern family, not a single monolith. ### Suggested stable slots The following `data-slot` names should exist in the first implementation: - `root` - `toolbar` - `input` - `content` - `table` - `header` - `row` - `cell` - `empty` - `pagination` - `actions` Additional slot names should only be added when they create a durable styling or testing hook. ### Suggested stable state surface The following public state hooks are likely worth exposing: - `data-loading` - `data-empty` - `data-selected` - `data-sort` - `data-density` `data-state` should still be used for finite row/header states where that is the clearest representation. ## Proposed column model The public API should accept a narrow column definition owned by this repo, even if it is internally adapted to TanStack. Example direction: ```ts type DataTableColumn = { id: string; header: ReactNode | ((column: DataTableColumnContext) => ReactNode); cell: (row: TData) => ReactNode; accessor?: keyof TData | ((row: TData) => unknown); sortable?: boolean; align?: "start" | "center" | "end"; width?: number | string; priority?: "primary" | "supporting" | "low"; }; ``` Key point: - keep the first public column shape small - do not surface every TanStack option - do not make users learn a second full configuration language on day one ## Composition model The table should integrate naturally with the patterns the repo already ships. ### DataTable should compose with - `Input` for search - `Select` and `DropdownMenu` for filters and view options - `Checkbox` for row selection - `Button` for primary and secondary actions - `Badge` for row metadata - `Tooltip` for truncated or status-heavy cells - `Popover` for compact detail reveal - `Sheet` for row inspection or bulk edit side panels - `EmptyState` for no-results and first-run moments - `Skeleton` for loading rows ### DataTable should not try to replace - `Sheet` detail workflows - `Dialog` destructive confirmations - `EmptyState` standalone onboarding/empty moments - `Form` validation and edit flows The table is the list surface. Other patterns handle adjacent work. ## Accessibility requirements The first release should treat accessibility as a primary contract, not a follow-up. ### Baseline requirements - semantic table structure for the standard grid use case - keyboard reachable sort controls - keyboard reachable row selection controls - visible focus styling on row actions and interactive header controls - accessible labeling for search, filters, and pagination controls - empty and loading states that remain understandable to screen readers - no motion dependency for critical state changes ### First-release accessibility decisions - Prefer semantic HTML table markup for the default implementation - Do not start with `role="grid"` unless we truly need spreadsheet-like keyboard control - Treat sorting controls as buttons inside header cells - Keep row selection explicit with `Checkbox`, not hidden click-to-select rows This fits the repo's current accessibility posture better than jumping straight to a complex grid interaction model. ## QA expectations The table should meet the repo's current QA bar from the first release. ### Unit and interaction coverage Minimum expected coverage: - renders header, rows, and cells with stable slots - sortable columns update controlled and uncontrolled sort state - selection toggles update row state and bulk action surface - loading state renders skeleton or loading rows - empty state renders when no rows remain after filtering - search/filter plumbing updates the rendered row set - pagination changes page and respects bounds ### Storybook coverage Minimum story recipe: - `Playground` - `States` - `Anatomy` - `Accessibility` Likely additional stories: - `Selection` - `Empty and Loading` ### Playwright smoke At least one smoke scenario should cover: - table renders - search narrows rows - sorting changes row order - selecting rows reveals a bulk-action affordance - opening row detail in `Sheet` still works ## Release plan ### Stage 0: RFC and composition rehearsal - finalize API direction in this RFC - build one realistic docs composition page that uses a table-like operational surface - identify missing supporting primitives before table code starts ### Stage 1: Headless core Ship a narrow `DataTable` that supports: - typed columns - sorting - selection - search - pagination - loading and empty states No virtualization, resizing, pinning, or editing. ### Stage 2: Workflow integration Add only after Stage 1 stabilizes: - row action menu patterns - bulk action bar - column visibility menu - row details opening in `Sheet` ### Stage 3: Scale features Only if justified by real product usage: - server-driven pagination hooks - column pinning - virtualization - advanced filters ## Likely file layout When implementation begins, the initial table slice should probably live in: ```txt packages/ui/src/components/data-table.tsx packages/ui/src/components/data-table.variants.ts packages/ui/src/components/data-table.test.tsx apps/docs/src/components/data-table.stories.tsx ``` If the implementation grows into a larger family, the repo may eventually want a dedicated component folder, but the first slice should stay consistent with the current flat component layout. ## Dependency recommendation ### Recommended - add `@tanstack/react-table` to `packages/ui` ### Not recommended for the first slice - virtualization package - drag and drop package - date package for table filters - full export/import package support The first implementation should minimize dependency expansion and keep the complexity budget focused on a single new capability. ## Open questions 1. Should the public column type stay fully source-owned, or should the first release expose a thin alias around TanStack column defs? 2. Should the first release include client-side search/filter state inside `DataTable`, or should filtering remain externally controlled? 3. Do we want a density variant in the first release, or should row height stay fixed until real product usage appears? 4. Should pagination UI be part of the root component family, or remain a separate companion component? 5. Should row selection be opt-in only, or should the root always reserve structure for it? ## Decision The next `DataTable` implementation should: - be built as an advanced pattern, not a primitive - be source-owned at the public API layer - use TanStack Table internally from the first implementation - ship as a narrow, headless/core-first operational table - defer scale features until real usage proves they are necessary