Skip to content

Basics

Loading example…

A slot is a named piece of the table you can swap for a component of your own. There are 65 of them, from Root down to SortIcon, and each receives fully computed props (classes, ARIA attributes, event handlers, state):

<DataTable
  slots={{
    Button: MyButton,
    Checkbox: MyCheckbox,
    Menu: MyMenu,
  }}
/>

Those three are worth noticing: about six primitive slots (button, icon button, checkbox, select, text input, menu) are enough to make the whole table look like your design system, because everything else is built from them.

Spread the props

A replacement that spreads what it receives keeps every behaviour it was given:

const MyHeaderCell: TableSlots<Person>['HeaderCell'] = (props) => (
  <th {...props} className={cx(props.className, 'my-header')} />
);

Drop the spread and you drop the sort handler, the aria-sort, the pinning offset and the data attributes with it.

Wrapping the default

useTableSlots().defaults gives you the original implementation, so an override can add rather than replace:

function HeaderCellWithHint(props) {
  const { HeaderCell: Default } = useTableSlots().defaults;
  return (
    <Default {...props}>
      {props.children}
      <InfoIcon title={props.column.columnDef.headerTooltip} />
    </Default>
  );
}

This matters more than it looks: the default header cell already contains the sort button, so rendering children beside your addition rather than wrapping it is what keeps a button out of a button.

Lighter than a slot

Most of the time you do not need a component. classNames, styles and slotProps take the same slot names in camelCase and merge rather than replace:

<DataTable
  classNames={{ cell: ({ column }) => (column.id === 'amount' ? 'tabular-nums' : undefined) }}
  slotProps={{ row: ({ row }) => ({ 'data-status': row.original.status }) }}
/>

Class names concatenate, styles shallow-merge and event handlers chain. Yours runs first, and it can stop the table's with event.preventTablekitDefault().

Customization

Slots compose with everything else: a slot can read the theme tokens, and behaviour belongs in handlers rather than in a slot that reimplements a click. For a full design-system adapter, see Overriding structure.

Limitations

  • Slots must forward refs to their DOM root. The table measures and focuses through them; a slot that swallows the ref breaks sizing and keyboard navigation.
  • A slot cannot change the table's structure. Moving a control somewhere else is a composable part, not a slot.
  • Replacing a semantic element with a div removes the semantics with it. A HeaderCell that is not a th is not a header cell to a screen reader.

API

  • TableSlots: every slot, its element and its props.
  • DataTableProps: slots, slotProps, classNames, styles, unstyled.
  • Hooks: useTableSlots for the defaults.