Overriding structure
Unstyled mode, primitive slots and composable parts: how to put a design system's own components in charge without losing the engine.
Basics
Loading example…
There are three levels of taking over the markup, and most projects need only the first.
Unstyled. unstyled drops the presentational CSS and keeps the structural rules: sticky
positioning, pinned offsets, the scroll container. You get a correct table with no opinions about
how it looks.
<DataTable unstyled slots={designSystemSlots} data={rows} columns={columns} />
Primitives. Six slots, Button, IconButton,
Checkbox, Select, TextInput and Menu, account for nearly every control in the table.
Replacing those is what makes an adapter small:
export const designSystemSlots = {
Button: DsButton,
IconButton: DsIconButton,
Checkbox: DsCheckbox,
Select: DsSelect,
TextInput: DsTextField,
Menu: DsMenu,
};
Layout. When the arrangement itself is wrong (search in the page header, filters in a
sidebar, pagination in a sticky footer), use DataTable.Root and the composable parts. The table
instance is shared, so every piece stays in sync:
const table = useDataTable({ data, columns });
<DataTable.Root table={table}>
<DataTable.Search table={table} />
<DataTable.Table />
<DataTable.Pagination />
</DataTable.Root>;
Shared defaults
TableDefaultsProvider applies slots, localization, icons and handlers to every table beneath
it, so an adapter is configured once for an application rather than per table. Props on a table
still win over the provider.
All the way down
If even the table element is wrong for what you are building (cards, a list, a chart), headless keeps the engine and gives up the rendering entirely.
Customization
The levels compose. An adapter usually means: unstyled, the six primitives, the provider, and tokens for what is left. That is a file, not a fork.
Limitations
- Unstyled is not style-free. The structural rules remain, because sticky headers and pinned columns are layout, not decoration.
- Composable parts still need the instance. They are not context-free; passing the table is what keeps them consistent.
- Replacing primitives replaces their accessibility too. A
Checkboxthat does not support the indeterminate state breaks tri-state select-all.
API
DataTableProps:unstyled,slots,slotProps,classNames,styles,handlers,icons,localization.- TableSlots: the full list, primitives included.
- Hooks:
useDataTablefor the composable form.