mcpField
N
niko-table-best-practices

niko-table-best-practices

github.com/semkooDevelopment7280 installsUpdated 3d ago
Repository

About niko-table-best-practices

niko-table-best-practices is a skill that guides developers building and configuring tables with Niko Table. It covers component composition, filters, drag-and-drop, server-side patterns, URL state, and editable grids, with guidance tailored to the user's existing table task.

Key features

  1. 01Recommend table component structure
  2. 02Configure search and advanced filters
  3. 03Set up row and column drag-and-drop
  4. 04Guide server-side table state
  5. 05Sync table state with URL
  6. 06Build editable Data Grids

Use cases

  1. 01Create a table with pagination and filters
  2. 02Add grouping or drag-and-drop to a table
  3. 03Connect table queries to a server backend
  4. 04Build a spreadsheet-style editable grid

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/semkoo/niko-table-registry --skill niko-table-best-practices

After installing, type/niko-table-best-practicesin Claude Code to confirm it loaded.

Details

From the project README

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.

PurposeImport 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, multi_select, range, date, date_range, 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:
    "registries": {
      "@niko-table": "https://niko-table.com/r/{name}.json"
    }
    
  4. 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.
  5. 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.
  6. 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 <table>: 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.

Comments

Markdown supported