Skip to content

Composable UI

Seizen Table provides multiple levels of API abstraction. Choose the level that fits your needs—from zero-config to fully custom rendering.

FeatureLevel 1 (High)Level 2 (Mid)Level 3 (Low)
Setup ComplexityMinimalModerateHigh
CustomizationLimitedHighComplete
Plugin Support✅ Yes✅ Yes❌ No
Context Menus✅ Yes✅ Yes❌ No
Layout Control❌ No✅ Yes✅ Yes
Best ForQuick setupCustom layouts with pluginsCompletely custom UI

The simplest approach—just pass a table instance and configuration:

import { SeizenTable, useSeizenTable } from "@izumisy/seizen-table";
function MyComponent() {
const table = useSeizenTable({ data, columns });
return <SeizenTable table={table} paginate={{ enable: true }} />;
}

Build custom layouts using composable components while keeping plugin support:

import { SeizenTable, SeizenTablePlugins, useSeizenTable } from "@izumisy/seizen-table";
function MyComponent() {
const table = useSeizenTable({ data, columns, plugins: [MyPlugin] });
return (
<SeizenTable.Root table={table}>
<SeizenTablePlugins.SidePanel position="left" />
<SeizenTable.Content>
<SeizenTablePlugins.Header />
<SeizenTable.Table>
<SeizenTable.Header />
<SeizenTable.Body />
</SeizenTable.Table>
<SeizenTablePlugins.Footer />
<SeizenTable.Paginator sizeOptions={[10, 25, 50]} />
</SeizenTable.Content>
<SeizenTablePlugins.SidePanel position="right" />
</SeizenTable.Root>
);
}

Level 3: Low-Level API (TanStack Table Direct)

Section titled “Level 3: Low-Level API (TanStack Table Direct)”

For maximum control, use TanStack Table API directly:

import { useSeizenTable, flexRender } from "@izumisy/seizen-table";
function CustomTable({ data, columns }) {
const table = useSeizenTable({ data, columns });
const tanstack = table._tanstackTable;
return (
<table>
<thead>
{tanstack.getHeaderGroups().map((headerGroup) => (
<tr key={headerGroup.id}>
{headerGroup.headers.map((header) => (
<th key={header.id}>
{flexRender(header.column.columnDef.header, header.getContext())}
</th>
))}
</tr>
))}
</thead>
<tbody>
{tanstack.getRowModel().rows.map((row) => (
<tr key={row.id}>
{row.getVisibleCells().map((cell) => (
<td key={cell.id}>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</td>
))}
</tr>
))}
</tbody>
</table>
);
}

Core table structure components:

ComponentHTML ElementDescription
SeizenTable.Root<div>Provider wrapper (required, provides context)
SeizenTable.Content<div>Main content area (between side panels)
SeizenTable.Table<div> + <table>Table wrapper with scrollable container
SeizenTable.Header<thead>Column headers with sorting and context menu
SeizenTable.Body<tbody>Row rendering with render props support
SeizenTable.Row<tr>Individual row with click handlers
SeizenTable.Cell<td>Individual cell with context menu support
SeizenTable.Paginator<div>Pagination controls
SeizenTable.Loader<div>Loading overlay for Remote Mode

Plugin-provided UI slots:

ComponentDescription
SeizenTablePlugins.SidePanelLeft/right side panels for plugin content
SeizenTablePlugins.HeaderPlugin header slots (e.g., filter bars)
SeizenTablePlugins.FooterPlugin footer slots
SeizenTablePlugins.InlineRowExpandable row content (e.g., row details)
SeizenTablePlugins.CellCustom cell rendering from plugins
SeizenTable.Root (providers + context)
├─ SeizenTablePlugins.SidePanel (position: "left")
├─ SeizenTable.Content (main content area)
│ ├─ SeizenTablePlugins.Header (plugin-provided toolbars)
│ ├─ SeizenTable.Table
│ │ ├─ SeizenTable.Loader (optional, via `before` prop)
│ │ ├─ SeizenTable.Header
│ │ └─ SeizenTable.Body
│ │ ├─ SeizenTable.Row
│ │ │ └─ SeizenTable.Cell
│ │ └─ SeizenTablePlugins.InlineRow (plugin-provided expandable rows)
│ ├─ SeizenTablePlugins.Footer (plugin-provided footers)
│ └─ SeizenTable.Paginator
└─ SeizenTablePlugins.SidePanel (position: "right")

For Remote Mode, use SeizenTable.Loader to show a loading state while fetching data:

const [isLoading, setIsLoading] = useState(false);
<SeizenTable.Table before={<SeizenTable.Loader loading={isLoading} />}>
<SeizenTable.Header />
<SeizenTable.Body />
</SeizenTable.Table>

Custom loader component:

<SeizenTable.Table
before={
<SeizenTable.Loader loading={isLoading}>
<MyCustomSpinner />
</SeizenTable.Loader>
}
>
<SeizenTable.Header />
<SeizenTable.Body />
</SeizenTable.Table>

SeizenTable.Body supports render props for custom row rendering:

<SeizenTable.Body>
{(row) => (
<>
<SeizenTable.Row
row={row}
className={row.original.isVIP ? "vip-row" : ""}
onClick={(row) => console.log("Clicked:", row.original)}
/>
<SeizenTablePlugins.InlineRow
row={row}
colSpan={row.getVisibleCells().length}
/>
</>
)}
</SeizenTable.Body>

When no children are provided, SeizenTable.Body automatically renders SeizenTable.Row and SeizenTablePlugins.InlineRow for each row.

Opting out of InlineRow:

<SeizenTable.Body>
{(row) => <SeizenTable.Row row={row} />}
</SeizenTable.Body>

SeizenTable.Cell supports custom children:

// Default: uses plugin Cell automatically
<SeizenTable.Cell cell={cell} row={row} />
// Custom content without plugin support
<SeizenTable.Cell cell={cell} row={row}>
<span className="custom">{cell.getValue()}</span>
</SeizenTable.Cell>
// Custom content with plugin support
<SeizenTable.Cell cell={cell} row={row}>
<SeizenTablePlugins.Cell cell={cell} column={cell.column} row={row}>
<span className="custom">{cell.getValue()}</span>
</SeizenTablePlugins.Cell>
</SeizenTable.Cell>

You can add custom elements anywhere in the layout:

<SeizenTable.Root table={table}>
<SeizenTablePlugins.SidePanel position="left" />
<SeizenTable.Content>
{/* Custom toolbar */}
<div className="custom-toolbar">
<button>Export</button>
<button>Refresh</button>
</div>
<SeizenTablePlugins.Header />
<SeizenTable.Table>
<SeizenTable.Header />
<SeizenTable.Body />
</SeizenTable.Table>
<SeizenTablePlugins.Footer />
{/* Custom footer content */}
<div className="table-info">
Showing {table.getData().length} records
</div>
<SeizenTable.Paginator />
</SeizenTable.Content>
<SeizenTablePlugins.SidePanel position="right" />
</SeizenTable.Root>

For completely custom rendering while keeping context menu support, use the provided hooks inside SeizenTable.Root.

HookDescription
useSeizenTableContext()Get the table instance from context
useContextMenu()Get handleCellContextMenu and handleColumnContextMenu functions
import {
SeizenTable,
useSeizenTableContext,
useContextMenu,
flexRender
} from "@izumisy/seizen-table";
function CustomTableBody() {
const table = useSeizenTableContext();
const { handleCellContextMenu } = useContextMenu();
return (
<>
{table._tanstackTable.getRowModel().rows.map((row) => (
<tr
key={row.id}
onClick={() => table.eventBus.emit("row-click", row.original)}
>
{row.getVisibleCells().map((cell) => (
<td
key={cell.id}
onContextMenu={(e) =>
handleCellContextMenu(e, cell, cell.column, row)
}
>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</td>
))}
</tr>
))}
</>
);
}
// Usage (must be inside SeizenTable.Root)
<SeizenTable.Root table={table}>
<SeizenTable.Content>
<SeizenTable.Table>
<SeizenTable.Header />
<tbody>
<CustomTableBody />
</tbody>
</SeizenTable.Table>
</SeizenTable.Content>
</SeizenTable.Root>