Skip to main content

Overview

The @kuzenbo/datatable package provides powerful datatable components and hooks built on TanStack Table. Create feature-rich tables with sorting, filtering, pagination, column resizing, and more.
This package is currently in preview and not yet published to npm. The API may change before the stable release.

Installation

Once published, install with:
npm install @kuzenbo/datatable @kuzenbo/core @kuzenbo/theme react react-dom

Exports

useDatatableState

Hook for managing datatable state (sorting, filtering, pagination)

MockDataTable

Pre-built datatable component for prototyping

createMockColumns

Utility to generate mock column definitions

Basic Usage

Using the Hook

import { useDatatableState } from '@kuzenbo/datatable/hooks/use-datatable-state';
import { Table } from '@kuzenbo/core/ui/table';
import { useMemo } from 'react';

interface User {
  id: number;
  name: string;
  email: string;
  role: string;
}

const data: User[] = [
  { id: 1, name: 'John Doe', email: '[email protected]', role: 'Admin' },
  { id: 2, name: 'Jane Smith', email: '[email protected]', role: 'User' },
];

export function UsersTable() {
  const columns = useMemo(() => [
    { accessorKey: 'name', header: 'Name' },
    { accessorKey: 'email', header: 'Email' },
    { accessorKey: 'role', header: 'Role' },
  ], []);

  const table = useDatatableState({
    data,
    columns,
    enableSorting: true,
    enableFiltering: true,
  });

  return (
    <Table>
      <Table.Header>
        {table.getHeaderGroups().map((headerGroup) => (
          <Table.Row key={headerGroup.id}>
            {headerGroup.headers.map((header) => (
              <Table.Head key={header.id}>
                {header.column.columnDef.header}
              </Table.Head>
            ))}
          </Table.Row>
        ))}
      </Table.Header>
      <Table.Body>
        {table.getRowModel().rows.map((row) => (
          <Table.Row key={row.id}>
            {row.getVisibleCells().map((cell) => (
              <Table.Cell key={cell.id}>
                {cell.getValue()}
              </Table.Cell>
            ))}
          </Table.Row>
        ))}
      </Table.Body>
    </Table>
  );
}

Mock DataTable

import { MockDataTable } from '@kuzenbo/datatable/ui/mock-data-table';
import { createMockColumns } from '@kuzenbo/datatable/utils/create-mock-columns';

export function PrototypeTable() {
  const columns = createMockColumns(5); // 5 columns
  const data = Array.from({ length: 20 }, (_, i) => ({
    id: i + 1,
    col1: `Value ${i + 1}`,
    col2: `Data ${i + 1}`,
    col3: Math.random() * 100,
    col4: new Date().toLocaleDateString(),
    col5: i % 2 === 0 ? 'Active' : 'Inactive',
  }));

  return <MockDataTable columns={columns} data={data} />;
}

Features

Sorting

import { useDatatableState } from '@kuzenbo/datatable/hooks/use-datatable-state';

const table = useDatatableState({
  data,
  columns,
  enableSorting: true,
  initialState: {
    sorting: [
      { id: 'name', desc: false },
    ],
  },
});

Filtering

import { useDatatableState } from '@kuzenbo/datatable/hooks/use-datatable-state';
import { Input } from '@kuzenbo/core/ui/input';
import { useState } from 'react';

export function FilterableTable() {
  const [globalFilter, setGlobalFilter] = useState('');

  const table = useDatatableState({
    data,
    columns,
    enableGlobalFilter: true,
    state: {
      globalFilter,
    },
    onGlobalFilterChange: setGlobalFilter,
  });

  return (
    <>
      <Input
        placeholder="Search..."
        value={globalFilter}
        onChange={(e) => setGlobalFilter(e.target.value)}
      />
      {/* Table rendering */}
    </>
  );
}

Pagination

import { useDatatableState } from '@kuzenbo/datatable/hooks/use-datatable-state';
import { Button } from '@kuzenbo/core/ui/button';

const table = useDatatableState({
  data,
  columns,
  enablePagination: true,
  initialState: {
    pagination: {
      pageIndex: 0,
      pageSize: 10,
    },
  },
});

// Pagination controls
<div className="flex gap-2">
  <Button
    onClick={() => table.previousPage()}
    disabled={!table.getCanPreviousPage()}
  >
    Previous
  </Button>
  <span>
    Page {table.getState().pagination.pageIndex + 1} of{' '}
    {table.getPageCount()}
  </span>
  <Button
    onClick={() => table.nextPage()}
    disabled={!table.getCanNextPage()}
  >
    Next
  </Button>
</div>

Column Resizing

const table = useDatatableState({
  data,
  columns,
  enableColumnResizing: true,
  columnResizeMode: 'onChange',
});

Row Selection

import { useDatatableState } from '@kuzenbo/datatable/hooks/use-datatable-state';
import { Checkbox } from '@kuzenbo/core/ui/checkbox';

const columns = [
  {
    id: 'select',
    header: ({ table }) => (
      <Checkbox
        checked={table.getIsAllRowsSelected()}
        onChange={table.getToggleAllRowsSelectedHandler()}
      />
    ),
    cell: ({ row }) => (
      <Checkbox
        checked={row.getIsSelected()}
        onChange={row.getToggleSelectedHandler()}
      />
    ),
  },
  // ... other columns
];

const table = useDatatableState({
  data,
  columns,
  enableRowSelection: true,
});

const selectedRows = table.getSelectedRowModel().rows;

Custom Cell Rendering

const columns = [
  {
    accessorKey: 'status',
    header: 'Status',
    cell: ({ getValue }) => {
      const status = getValue() as string;
      return (
        <Badge variant={status === 'active' ? 'success' : 'default'}>
          {status}
        </Badge>
      );
    },
  },
  {
    accessorKey: 'actions',
    header: 'Actions',
    cell: ({ row }) => (
      <Button
        size="sm"
        variant="ghost"
        onClick={() => handleEdit(row.original)}
      >
        Edit
      </Button>
    ),
  },
];

Advanced Usage

Server-Side Operations

import { useDatatableState } from '@kuzenbo/datatable/hooks/use-datatable-state';
import { useState, useEffect } from 'react';

export function ServerSideTable() {
  const [data, setData] = useState([]);
  const [loading, setLoading] = useState(false);
  const [pageCount, setPageCount] = useState(0);

  const table = useDatatableState({
    data,
    columns,
    pageCount,
    manualPagination: true,
    manualSorting: true,
    manualFiltering: true,
  });

  useEffect(() => {
    const fetchData = async () => {
      setLoading(true);
      const { pageIndex, pageSize } = table.getState().pagination;
      const response = await fetch(
        `/api/data?page=${pageIndex}&size=${pageSize}`
      );
      const { rows, totalPages } = await response.json();
      setData(rows);
      setPageCount(totalPages);
      setLoading(false);
    };

    fetchData();
  }, [table.getState().pagination]);

  return loading ? <Spinner /> : <Table>{/* ... */}</Table>;
}

Dependencies

  • @tanstack/react-table - Headless table library
  • tailwind-variants - Styling
  • tailwind-merge - Class merging

Peer Dependencies

{
  "@kuzenbo/core": "^0.0.7",
  "@kuzenbo/theme": "^0.0.7",
  "react": "^19.0.0",
  "react-dom": "^19.0.0"
}

TypeScript

Fully typed with TanStack Table types:
import { useDatatableState } from '@kuzenbo/datatable/hooks/use-datatable-state';
import type { ColumnDef } from '@tanstack/react-table';

interface Product {
  id: number;
  name: string;
  price: number;
}

const columns: ColumnDef<Product>[] = [
  { accessorKey: 'name', header: 'Name' },
  { accessorKey: 'price', header: 'Price' },
];

const table = useDatatableState<Product>({
  data,
  columns,
});

Next Steps

Table Component

Learn about the base Table component

Build docs developers (and LLMs) love