Skip to content

Styling Cells, Headers, and Rows

The visible parts of the table — cells, headers, the outer frame — all accept Tailwind classes the way any component in your app does. Reach for column-level styling when a single column needs to look different (a status pill, a right-aligned number, a muted secondary line) and wrapper-level styling when the change applies to the whole table.

import { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader } from '@/components/ui/data-table'
import { localModel } from '@virtuoso.dev/data-table'

const rows = [
  { name: 'Standing Desk', status: 'Launch ready', theme: 'Office' },
  { name: 'USB-C Dock', status: 'Low stock', theme: 'Peripherals' },
  { name: 'Noise-canceling Headset', status: 'Backorder', theme: 'Audio' },
]

const model = localModel({ data: rows })

export default function App() {
  return (
    <DataTable className="rounded-xl border-2 border-slate-900/10 shadow-sm" model={model} style={{ height: 280 }}>
      <DataTableColumn field="name">
        <DataTableColumnHeader>Product</DataTableColumnHeader>
        <DataTableCell className="font-medium">{({ cellValue }) => String(cellValue)}</DataTableCell>
      </DataTableColumn>
      <DataTableColumn field="theme">
        <DataTableColumnHeader>Theme</DataTableColumnHeader>
        <DataTableCell>
          {({ cellValue }) => <span className="rounded-full bg-slate-100 px-2 py-1 text-xs font-medium">{String(cellValue)}</span>}
        </DataTableCell>
      </DataTableColumn>
      <DataTableColumn field="status">
        <DataTableColumnHeader>Status</DataTableColumnHeader>
        <DataTableCell>
          {({ cellValue }) => (
            <span
              className={
                cellValue === 'Backorder'
                  ? 'font-medium text-red-600'
                  : cellValue === 'Low stock'
                    ? 'font-medium text-amber-600'
                    : 'font-medium text-emerald-600'
              }
            >
              {String(cellValue)}
            </span>
          )}
        </DataTableCell>
      </DataTableColumn>
    </DataTable>
  )
}

className on DataTableCell and DataTableColumnHeader applies to the cell or header container — the slot the table positions and sizes. Render-function children let you wrap the value in whatever markup you need (badges, two-line layouts, conditional colors).

className on <DataTable> reaches the outer frame around the scroll viewport. Use it for borders, rounded corners, shadow, and the table’s overall background.

<DataTable className="rounded-xl border bg-card shadow-sm" model={model}>
  {/* columns */}
</DataTable>

For pixel sizing, prefer style (style={{ height: 360 }}) — the table needs a measurable height to virtualize, and Tailwind’s h-* utilities work too, as long as the resolved height is real (not a percentage of an unmeasured parent).

Two adjacent concerns have their own pages:

  • Replacing the table’s structural components (Row, GroupHeader, sticky wrappers, scroll element) lives in Replacing Internals. That’s the components prop pattern.
  • Default classes baked into the shadcn wrapper — change those by editing @/components/ui/data-table directly. See The shadcn Wrapper for the wrapper anatomy.