Styling guide

Keep the data engine. Own the interface.

Use the default theme, override its CSS variables, or opt out of visual chrome and apply Tailwind classes to semantic slots.

Tailwind-first chrome

unstyled leaves virtualization, sizing, keyboard behavior, and accessibility in place while removing the package visual preset.

tsx
<DataTable
  unstyled
  classNames={{
    root: "overflow-hidden rounded-xl border border-slate-200 bg-white shadow-sm",
    body: "bg-white",
    expanderButton: ({ expanded }) =>
      expanded ? "rounded bg-slate-900 text-white" : "rounded border border-slate-300",
    expandedPanel: "border-y border-slate-200 bg-slate-50 p-5",
  }}
  dataSource={rows}
  columnSettings={columns}
  renderExpandedRow={({ row, collapse }) => (
    <section className="grid gap-3 md:grid-cols-2">
      <div><b>{row.name}</b><p>{row.description}</p></div>
      <button onClick={collapse}>Close details</button>
    </section>
  )}
/>

Expanded rows

The +/- gutter is immediately left of row selection. Several rows can remain open. Detail-panel JSX is measured with ResizeObserver, so images, async content, and responsive layouts update virtual row height automatically.

Use expandedRows and onExpandedRowsChange when expansion belongs in application state. Use isRowExpandable to omit the control for individual rows.

Default theme tokens

Pass typed tokens directly or set the same variables in CSS. Tokens cover color, typography, spacing, controls, focus, selection, scrollbars, overlays, editors, menus, and expanded panels.

tsx
<DataTable tokens={{
  "--dt-bg": "#ffffff",
  "--dt-cell": "#ffffff",
  "--dt-surface": "#f8fafc",
  "--dt-border": "#cbd5e1",
  "--dt-border-subtle": "#e2e8f0",
  "--dt-accent": "#4f46e5",
  "--dt-expanded-bg": "#f8fafc",
  "--dt-expanded-border": "#cbd5e1",
  "--dt-expanded-padding-x": "1.25rem",
}} />
CSS
.billing-table {
  --dt-font: ui-sans-serif, system-ui;
  --dt-header-bg: theme(colors.slate.50);
  --dt-focus-ring: theme(colors.indigo.500);
  --dt-menu-bg: theme(colors.white);
  --dt-expanded-border: var(--dt-border);
}

Semantic class slots

Slots target root chrome, toolbar/formula bar, headers, contextual cells, expansion, states, footer, scrollbars, editors, menus, dialogs, settings, validation, and notices. Row and cell slots accept resolver functions when they need row values.

tsx
classNames={{
  dataCell: ({ row, colId }) => row.overdue && colId === "balance"
    ? "font-semibold text-red-700" : undefined,
  expanderButton: ({ expanded }) => expanded ? "bg-slate-900 text-white" : "",
  expandedPanel: ({ row }) => row.priority ? "border-amber-300 bg-amber-50" : "",
  dialog: "rounded-xl border border-slate-200 bg-white shadow-2xl",
}}

Portal surfaces

Use the menu, dialog, settings, validation, and notice slots for portaled UI. Keep theme tokens on those surfaces when mounting custom portals of your own.