semkoo/niko-table-registry · Archived

niko-table-best-practices

>- Integrate and use Niko Table (shadcn-compatible data tables with TanStack Table) and the editable Data Grid. Use when the user mentions Niko Table, niko-table.com, data table with shadcn, TanStack Table, building a sortable or filterable table, faceted filters, advanced filter menu, row or column drag-and-drop, server-side pagination, server-side grid, infinite scroll, Drizzle ORM, nuqs, URL state, shareable or bookmarkable table, row expansion, tree table, row selection, editable spreadshee…

First seen Mar 11, 2026

Installation

$ npx skills add semkoo/niko-table-registry --skill niko-table-best-practices

Stronger alternatives

This repository is archived — consider an actively maintained alternative.

Similar popular skills

Related neighbors and high-traction skills in the same topics — useful to compare before installing.

More details

Agent compatibility

Declared targets from SKILL.md / docs. Unmarked agents are not listed — the skill may still install via the CLI.

Claude Code Not declared
Cursor Not declared
Codex Not declared
GitHub Copilot Not declared
Windsurf Not declared
Gemini CLI Not declared
Cline Not declared
OpenCode Not declared

Repository health

Stars 62
License LICENSE
Default branch main
Open issues 1
Status Archived

Package contents

Files included with this skill beyond the listing page.

  • skill md SKILL.md 22,583 B
  • docs SUMMARY.md 864 B

History

  1. First seen on skills.sh
  2. First recorded snapshot · 71 installs

SKILL.md

Niko Table Integration Guide

A skill for building and configuring data tables with Niko Table: structure, filters (search, faceted, advanced), column menus, DnD, server-side patterns, and the editable Data Grid.

At a high level:

  • New table: DataTableRoot → ToolbarSection (optional) → DataTable → Header + Body (Skeleton, EmptyBody) → Pagination. Use direct file imports only (no barrel exports). Install @niko-table/data-table for the owner; à-la-carte controls depend on @niko-table/data-table-core (context + structure, no Root) when you own the TanStack instance.
  • Adding filters: Toolbar = DataTableSearchFilter, DataTableFacetedFilter, DataTableFilterMenu. Column headers = DataTableColumnFacetedFilterMenu, DataTableColumnSliderFilterMenu, DataTableColumnDateFilterMenu. Set column meta (variant, options, etc.) and enableColumnFilter: true.
  • Row/column DnD: Row DnD needs getRowId, DataTableRowDndProvider outside DataTable; don’t combine row DnD with sorting/filtering. Column DnD is safe with everything.
  • Grouping: Mount DataTableColumnGroupOptions in DataTableColumnActions (or config.enableGrouping). Use aggregationFn / aggregatedCell for rollups and getGroupingValue for buckets (e.g. month). Don’t combine with row DnD or with tree getSubRows — tree = nested data; grouping = flat rows bucketed by column. See Grouping Table / Tree Table on niko-table.com.
  • Server-side: config.manualPagination / manualSorting / manualFiltering + config.pageCount, pass totalCount to DataTablePagination, and set maxHeight on DataTable so page-size changes scroll. Structure it around a serializable wire contract — ONE function fetch(query: { page, pageSize, sorting, search, columnFilters }) → { data, total, facets } — so any backend (SQL, Drizzle, Prisma, Supabase, REST) plugs in; see Server-Side Table example and the Drizzle ORM guide at niko-table.com.
  • URL state (nuqs): Wrap app with NuqsAdapter; use useQueryStates with parsers for pagination, sort, filters, search; pass URL-derived state into DataTableRoot and wire onPaginationChange / onSortingChange / onColumnFiltersChange / onGlobalFilterChange to setUrlParams.
  • Large lists: Use DataTableVirtualizedBody (from core/structure) instead of DataTableBody for 10k+ rows; same children (Skeleton, EmptyBody). See Virtualization Table example.
  • Sidebar: Use DataTableAside (and trigger) for a detail panel next to the table. See Aside Table example.
  • Data Grid: useDataGrid + <DataGrid grid={grid}> wrapping DataTable inside DataTableRoot. Opt-in children for clipboard/fill/move. Cell editors via <DataGridCell> + Grid*Cell. Install @niko-table/data-table-grid (+ data-table-grid-changes for persistence). Mount <DataTableColumnResize /> inside the grid so columns flex-fill the width, and pass className="space-y-2 outline-none" to <DataGrid> for spacing. For grids backed by a server, don't paginate — stream chunks on scroll (see Server-Side patterns below). See Basic Grid / Data Grid docs on niko-table.com.

Your job when using this skill is to figure out where the user is — new table, adding filters/DnD/grouping, fixing imports, wiring server-side, URL state (nuqs), row expansion, tree table, row selection, or editable Data Grid — and give them the right structure, imports, and patterns. If they’re vague (“I want a table”), suggest the minimal template and point to niko-table.com for examples. If they already have a table and want faceted filters or the advanced filter menu, jump to the Filtering section. If they want spreadsheet editing, go to Data Grid. Stay flexible: some users want copy-paste snippets; others want to understand the two-layer (DataTable vs Table) pattern.

Full docs and examples: https://niko-table.com. Registry: https://niko-table.com/r/{name}.json in components.json under registries["@niko-table"].

Quick Reference

  • Docs and examples: https://niko-table.com (installation, examples, API overview)
  • Registry URL: https://niko-table.com/r/{name}.json — add to components.json under registries["@niko-table"]

Communicating with the user

Users may be new to Niko Table or to the shadcn/TanStack stack. When in doubt, briefly explain why direct imports matter (tree-shaking, no barrel files) and why getRowId is needed for row DnD. Mention that most DataTableRoot config is auto-detected from the components they use, so they only need to pass config when overriding or for manual/server-side. For deep reference (all add-ons, full API), point to niko-table.com.

When not to use: If the project clearly uses another table library (e.g. AG Grid, MUI Data Grid, TanStack Table without this registry), don’t force Niko Table — suggest the appropriate pattern for their stack.

Table Structure

All table UI must live inside DataTableRoot. Recommended composition:

DataTableRoot (required)
  → DataTableToolbarSection (optional: search, filters, view menu)
  → DataTable
      → DataTableHeader
      → DataTableBody
          → DataTableSkeleton   (when loading)
          → DataTableEmptyBody  (when no data)
  → DataTablePagination (optional)

Optional: Wrap the table (or DataTableRoot) in DataTableErrorBoundary from @/components/niko-table/core/data-table-error-boundary so render errors show a fallback UI instead of breaking the page.

Imports — No Barrel Exports

Use direct file paths only. Do not use index.ts re-exports for core, components, or filters.

Purpose Import path
Root & table @/components/niko-table/core/data-table-root, @/components/niko-table/core/data-table
Structure @/components/niko-table/core/data-table-structure (Header, Body, Skeleton, EmptyBody)
Toolbar / filters @/components/niko-table/components/data-table-toolbar-section, data-table-search-filter, data-table-pagination, etc.
Types @/components/niko-table/types (e.g. DataTableColumnDef)

Examples:

import { DataTableRoot } from "@/components/niko-table/core/data-table-root"
import { DataTable } from "@/components/niko-table/core/data-table"
import {
  DataTableHeader,
  DataTableBody,
  DataTableSkeleton,
  DataTableEmptyBody,
} from "@/components/niko-table/core/data-table-structure"
import { DataTableToolbarSection } from "@/components/niko-table/components/data-table-toolbar-section"
import { DataTableSearchFilter } from "@/components/niko-table/components/data-table-search-filter"
import { DataTablePagination } from "@/components/niko-table/components/data-table-pagination"
import type { DataTableColumnDef } from "@/components/niko-table/types"

Two-Layer Component Pattern

  • Components (DataTable*): Context-aware; use useDataTable() internally. Import from components/. Prefer these for normal usage (no table prop).
  • Filters (Table*): Accept table prop; import from filters/. Use when building custom components or managing the table instance yourself.

Use DataTable components from their direct paths (e.g. data-table-pagination) for context-based usage; use Table from filters when you need the low-level API.

Column Definitions

  • Type: DataTableColumnDef<TData>[] from @/components/niko-table/types.
  • Use accessorKey and header. For sortable/filterable columns, use DataTableColumnHeader, DataTableColumnTitle, and DataTableColumnSortMenu (and column filter menus as needed).
  • meta for filter config: label, placeholder, variant (text, select, multiselect, range, date, daterange, number, boolean), options (static { label, value }[]), autoOptions (generate from data), unit (e.g. "$" for range), showCounts, dynamicCounts, mergeStrategy ("preserve" | "augment" | "replace" for options). Set enableColumnFilter: true when using column filters.

Filtering

Toolbar filters (inside DataTableToolbarSection)

  • Global search: DataTableSearchFilter — single search input; no column meta required.
  • Advanced (rule-based): DataTableFilterMenu — command-palette style; add multiple filter rules with AND/OR. Column meta.variant (e.g. text, select, range, date) determines filter type. Install @niko-table/data-table-filter-menu and optionally checkbox from Shadcn for multi-select.
  • Faceted (inline): DataTableFacetedFilter — one filter per column; shows options with counts. Use accessorKey to tie to a column; options come from column meta.options or meta.autoOptions. Use multiple for multi-select. limitToFilteredRows restricts options to current result set.
  • Other toolbar: DataTableSliderFilter, DataTableDateFilter for range/date in toolbar; DataTableClearFilter to clear all filters.

Column-level filter menus (in header)

Render inside the column header next to DataTableColumnTitle and DataTableColumnSortMenu:

  • Faceted: DataTableColumnFacetedFilterMenu — select/multi-select with counts. Column needs meta.options or meta.autoOptions; optional meta.showCounts, meta.dynamicCounts, meta.mergeStrategy. Use FILTER_VARIANTS.TEXT (or .NUMBER, .DATE) on DataTableColumnSortMenu when needed.
  • Slider (range): DataTableColumnSliderFilterMenu — numeric range; column meta.variant: "range", optional meta.unit.
  • Date: DataTableColumnDateFilterMenu — date or date-range; column meta.variant: "date" or "date_range".

Example column with faceted + sort:

import { DataTableColumnHeader, DataTableColumnTitle } from "@/components/niko-table/components/data-table-column-header"
import { DataTableColumnSortMenu } from "@/components/niko-table/components/data-table-column-sort"
import { DataTableColumnFacetedFilterMenu } from "@/components/niko-table/components/data-table-column-faceted-filter"
import { FILTER_VARIANTS } from "@/components/niko-table/lib/constants"

{
  accessorKey: "category",
  header: () => (
    <DataTableColumnHeader>
      <DataTableColumnTitle />
      <DataTableColumnSortMenu variant={FILTER_VARIANTS.TEXT} />
      <DataTableColumnFacetedFilterMenu multiple limitToFilteredRows={false} />
    </DataTableColumnHeader>
  ),
  meta: {
    label: "Category",
    options: categoryOptions,
    mergeStrategy: "augment",
    dynamicCounts: true,
    showCounts: true,
  },
  enableColumnFilter: true,
}

Example toolbar with search + faceted + advanced + clear:

<DataTableToolbarSection>
  <DataTableSearchFilter placeholder="Search..." />
  <DataTableFacetedFilter accessorKey="category" title="Category" options={categoryOptions} multiple />
  <DataTableFacetedFilter accessorKey="brand" limitToFilteredRows />
  <DataTableFilterMenu autoOptions dynamicCounts showCounts mergeStrategy="augment" />
  <DataTableViewMenu />
  <DataTableClearFilter />
</DataTableToolbarSection>

Add-ons: data-table-search-filter, data-table-filter-menu, data-table-faceted-filter, data-table-clear-filter, data-table-slider-filter, data-table-date-filter, data-table-column-faceted-filter, data-table-column-slider-filter, data-table-column-date-filter. Full list at niko-table.com.

DataTableRoot Config and State

Most config is auto-detected from the components you render: e.g. DataTablePagination enables pagination, DataTableSearchFilter / DataTableFilterMenu / DataTableFacetedFilter / column filter menus enable filtering, DataTableColumnHeader / DataTableColumnSortMenu enable sorting, DataTableSelectionBar enables row selection. You only need to pass config when overriding defaults or for features that aren’t declared by a child (e.g. server-side or manual modes).

  • config (override when needed): manualPagination, manualSorting, manualFiltering, pageCount, initialPageSize, initialPageIndex, autoResetPageIndex, autoResetExpanded, or to turn off a feature. Full list: enablePagination, enableSorting, enableFilters, enableRowSelection, enableExpanding, etc.
  • state: Optional controlled state plus onPaginationChange, onSortingChange, onColumnFiltersChange, onColumnVisibilityChange, onRowSelectionChange, onExpandedChange, onRowSelection.
  • getRowId: Optional (row, index) => string. Use stable unique IDs (e.g. (row) => row.id) when using row DnD, row selection, or expansion — default index-based IDs break after reorder.
  • Loading: Set isLoading on DataTableRoot; render DataTableSkeleton and DataTableEmptyBody inside DataTableBody.

URL state (nuqs)

Sync table state (pagination, sorting, filters, search) with the URL for shareable, bookmarkable views. Use nuqs: install nuqs and wrap the app with the framework-specific NuqsAdapter in the app root (nuqs/adapters/next/app, nuqs/adapters/next/pages, or nuqs/adapters/react). See nuqs adapters docs for setup.

  • Parsers: Define parsers with parseAsInteger.withDefault(0) for pageIndex/pageSize, parseAsJson for sort/filters, parseAsString for search. Match TanStack Table state shape so URL params map 1:1 to state.
  • Wiring: useQueryStates(parsers, { history: "replace" }) → [urlParams, setUrlParams]. Derive pagination, sorting, columnFilters, globalFilter from urlParams (e.g. via useMemo). Pass to DataTableRoot as state={{ pagination, sorting, columnFilters, globalFilter }}. In onPaginationChange, onSortingChange, onColumnFiltersChange, onGlobalFilterChange, call setUrlParams with the updated slice so the URL stays in sync.
  • DataTableFilterMenu: When using the filter menu with nuqs, filters in the URL are often stored as extended filter objects; convert between TanStack ColumnFiltersState and that shape in the handlers using serializeFiltersForUrl / normalizeFiltersFromUrl (exported from filters/table-filter-menu — they strip/regenerate filterId to keep URLs short and input focus stable). See Advanced Nuqs Table and Server-Side Nuqs Table examples at niko-table.com.

Installation (for projects without Niko Table)

  1. Prerequisites: React project, Shadcn UI, TailwindCSS, TypeScript.
  2. Client components: Table components use React state and hooks; add "use client" at the top of any file that renders DataTableRoot or Niko Table components (required in Next.js App Router and similar).
  3. Registry: In components.json, add:

``json "registries": { "@niko-table": "https://niko-table.com/r/{name}.json"; } ``

  1. Core: shadcn@latest add @niko-table/data-table (pulls data-table-core + data-table-ui + Root/chrome; installs/updates components/ui/table.tsx with TableComponent and a keyboard-focusable scroll container). For controls-only / own TanStack instance: install the control items — they resolve data-table-core without Root or table.tsx. Add @niko-table/data-table-ui only if you need our table primitive.
  2. Add-ons (examples): @niko-table/data-table-pagination, @niko-table/data-table-search-filter, @niko-table/data-table-view-menu, @niko-table/data-table-sort-menu, @niko-table/data-table-filter-menu, plus column-level filter/sort components as needed. Match names from the registry at niko-table.com.
  3. Server-side: For manualPagination/manualSorting/manualFiltering, set config.pageCount (e.g. Math.ceil(totalCount / pageSize)) and pass totalCount to DataTablePagination when using server-driven data.

Drag and Drop

  • Row DnD: Do not combine with sorting or filtering (data order conflicts). Requires getRowId={(row) => row.id} (or similar stable ID) — index-based IDs break after reorder. Wrap with DataTableRowDndProvider outside <DataTable> (DnD context uses divs that cannot live inside <table>). Use DataTableDndBody and DataTableRowDragHandle inside. Add a drag-handle column with cell: ({ row }) => <DataTableRowDragHandle rowId={row.id} />.
  • Column DnD: Safe to combine with other features. Use DataTableColumnDndProvider, DataTableDraggableHeader, DataTableDragAlongCell, and the corresponding core structure components (including virtualized variants when needed).

Styling

Use semantic color tokens (e.g. bg-success, text-destructive) instead of hardcoded Tailwind colors.

Pitfalls to Avoid

  • No barrel imports: Do not import from @/components/niko-table or .../core or .../components without the full path to the file (e.g. .../core/data-table-root).
  • Row DnD without getRowId: Row drag-and-drop requires stable row IDs; pass getRowId={(row) => row.id} (or your ID field). Omitting it or using index causes wrong behavior after reorder.
  • Row DnD with sorting/filtering: Do not enable row reorder and sort/filter together; data order becomes ambiguous.
  • DataTableRowDndProvider inside &lt;table&gt;: The provider must wrap the whole DataTable from outside; its markup cannot go inside <table>.

Minimal Table Template

"use client"

import { DataTableRoot } from "@/components/niko-table/core/data-table-root"
import { DataTable } from "@/components/niko-table/core/data-table"
import {
  DataTableHeader,
  DataTableBody,
  DataTableSkeleton,
  DataTableEmptyBody,
} from "@/components/niko-table/core/data-table-structure"
import { DataTableToolbarSection } from "@/components/niko-table/components/data-table-toolbar-section"
import { DataTableSearchFilter } from "@/components/niko-table/components/data-table-search-filter"
import { DataTablePagination } from "@/components/niko-table/components/data-table-pagination"
import type { DataTableColumnDef } from "@/components/niko-table/types"

type User = { id: string; name: string; email: string }

const columns: DataTableColumnDef<User>[] = [
  { accessorKey: "name", header: "Name" },
  { accessorKey: "email", header: "Email" },
]

export function UsersTable({ data, isLoading }: { data: User[]; isLoading?: boolean }) {
  return (
    <DataTableRoot data={data} columns={columns} isLoading={isLoading}>
      <DataTableToolbarSection>
        <DataTableSearchFilter placeholder="Search..." />
      </DataTableToolbarSection>
      <DataTable>
        <DataTableHeader />
        <DataTableBody>
          <DataTableSkeleton />
          <DataTableEmptyBody />
        </DataTableBody>
      </DataTable>
      <DataTablePagination />
    </DataTableRoot>
  )
}

Data Grid

Editable spreadsheet-style grid built on the same DataTableRoot / virtualized body. Do not invent a separate widget API.

DataTableRoot data={grid.rows} getRowId={(r) => r.id}
  → DataGrid grid={grid}
      → opt-in: DataGridClipboard, DataGridFillHandle, DataGridMove, …
      → DataTable → VirtualizedHeader + VirtualizedBody (fixed height)
  • Engine: useDataGrid({ columnIds, createEmptyRow, initialRows? }) — uncontrolled rows, focus/selection by { rowId, columnId }, undo/redo.
  • Cells: Wrap editors in <DataGridCell row={…} columnId={…}>. Use GridTextCell, GridNumberCell, GridCheckboxCell, GridDateCell, GridSelectCell / GridComboboxCell. Validity comes from your resolve → CellState.
  • Opt-in features: Mount as children of <DataGrid> only — unmounted = tree-shaken.
  • Persistence: Separate install @niko-table/data-table-grid-changes → useGridChanges(grid, { initialRows }).
  • Server-side grid: no pagination — stream rows on scroll via DataTableVirtualizedBody's onNearEnd + prefetchThreshold. Load each chunk with grid.updateRows(() => rows, { history: false }) + changes.reset(rows), merging around changes.dirtyRowIds so unsaved edits survive. Save via changes.getChangeSet() → POST { created, updated, deleted } → changes.reconcile({ succeededIds, failedIds }); failed rows stay dirty (highlight with failedRowIds). Raise useDataGrid's maxRows (default 200) above the expected loaded count. See Server-Side Grid example.
  • Docs: https://niko-table.com/data-grid/introduction/ · https://niko-table.com/examples/basic-grid/

Where to Learn More

  • Online: https://niko-table.com — installation, examples, component overview, API. Table examples: Simple / Basic / Faceted / Advanced / Nuqs / Server-Side / Server-Side Nuqs / Infinite Scroll / Row DnD / Column DnD / Virtualization / Aside; the Drizzle ORM guide (with a live mocked demo that shows the generated SQL) implements the server-side wire contract. Data Grid examples: Basic Grid, Cell Types, Validation, Dynamic Columns, Persistence, Server Side. For resilience: DataTableErrorBoundary (core/data-table-error-boundary).
  • Skills (AI): https://niko-table.com/getting-started/skills/ — how to install and use this skill.
  • Docs guidelines: https://niko-table.com/contributing/documentation-guidelines/
  • In-repo (when working in niko-table-registry): src/content/docs/ — e.g. getting-started/installation.mdx, niko-table/overview/, data-grid/overview/, examples/-table.mdx, examples/-grid.mdx.