Data Table

@sushindustries/ui

The same table with TanStack Table under it: sorting, filtering and paging that somebody else has already tested.

7 min read
View as MarkdownEdit on GitHub

Composing it

A column is an id into the row plus how that column is meant to be read. numeric right-aligns it, mono makes it breakable for a path or a hash, and cell takes over the rendering when the raw value is not what a reader wants.

const columns = [
	{ id: "path", header: "Path", mono: true },
	{ id: "kind", header: "Kind", sortable: true },
	{
		id: "tokens",
		header: "Tokens",
		numeric: true,
		sortable: true,
		cell: (row) => row.tokens.toLocaleString(),
	},
];
tsx

The frame is overflow-x-auto max-w-full border rounded-xl bg-1 in the markup, not a block class. That is what guarantees a table's border and radius match the cards beside it rather than drifting from them.

Density and banding

Both are attributes on the table, so a caller cannot half-apply one by passing a stray class name.

PropValueFor
densitycomfortable (default)A table somebody reads a few rows of
densitycompactHalf the padding, same type size. For scanning fifty rows, where the padding is most of the height
stripedtrueBands alternate rows, so the eye cannot slip between the first column and the last. Earns its place on a wide table

Type size is the same in both densities. Shrinking the text to fit more of it is where a dense table stops being readable and becomes a screenshot of a table. And banding on a three-column table has no distance to slip across, so there it is decoration that makes every second row look selected.

Sorting is somebody else's tested code

TanStack Table v9 does the row models and the comparators; this adds the markup and the class names, which is the half a headless library deliberately has no opinion about. v9 is not v8 - it takes an explicit features object and create*RowModel, so v8's shape type-errors here rather than rendering an empty table.

When not to use it

TableTableA table that is a table: declared columns, right-aligned numbers, sideways scroll in its own frame.@sushindustries/ui · content is the same look with no dependency, and it is the right answer for a table that is read once and sorted never. Filtering, pagination and grouping are not here either: each is one more feature slot when something needs it, and until then they are code nobody ships.