# Data table: Sticky header

> Give the container a max height with `containerClassName` and set `stickyHeader`. The header stays put and gains a hairline once the rows scroll under it. Shift-drag over the checkboxes scrolls the box as you near its edge.

Example: https://hextaui.com/docs/data-table/examples/sticky-header
Component docs: https://hextaui.com/docs/data-table.md

## Add this example

```bash
npx shadcn@latest add https://hextaui.com/r/data-table-sticky-header.json
```

This adds `components/examples/data-table/sticky-header.tsx` with the Data table component and the HextaUI theme tokens.

## Source

```tsx title="components/examples/data-table/sticky-header.tsx"
"use client"

import { Badge, BadgeDot } from "@/components/ui/badge"
import {
  createDataTableColumns,
  DataTable,
  DataTableColumnHeader,
  DataTableContent,
  DataTablePagination,
  DataTableSearch,
  DataTableSelectAll,
  DataTableSelectRow,
  DataTableToolbar,
  DataTableViewOptions,
  useDataTable,
} from "@/components/ui/data-table"

type Payment = {
  id: string
  email: string
  status: "paid" | "pending" | "failed"
  amount: number
  method: string
  country: string
  date: string
}

const statuses = ["paid", "pending", "failed"] as const
const methods = ["Card", "PayPal", "Bank", "Apple Pay"]
const countries = ["United States", "Germany", "India", "Japan", "Brazil"]
const names = ["olivia", "jackson", "isabella", "william", "sofia", "liam"]

function makePayments(count: number): Payment[] {
  return Array.from({ length: count }, (_, index) => ({
    id: `pay_${String(index + 1).padStart(4, "0")}`,
    email: `${names[index % names.length]}.${Math.floor(index / names.length) + 1}@example.com`,
    status: statuses[(index * 7) % 3],
    amount: Math.round(((index * 7919) % 100000) / 10) / 10 + 5,
    method: methods[index % methods.length],
    country: countries[(index * 3) % countries.length],
    date: `2026-${String((index % 9) + 1).padStart(2, "0")}-${String((index % 27) + 1).padStart(2, "0")}`,
  }))
}

const payments = makePayments(1000)

const currency = new Intl.NumberFormat("en-US", {
  style: "currency",
  currency: "USD",
})

const statusVariant = {
  paid: "success",
  pending: "warning",
  failed: "destructive",
} as const

const helper = createDataTableColumns<Payment>()

const columns = helper.columns([
  helper.display({
    id: "select",
    header: ({ table }) => <DataTableSelectAll table={table} />,
    cell: ({ row }) => <DataTableSelectRow row={row} />,
    enableSorting: false,
    enableHiding: false,
  }),
  helper.accessor("email", {
    header: ({ column }) => (
      <DataTableColumnHeader column={column} title="Email" />
    ),
  }),
  helper.accessor("status", {
    header: ({ column }) => (
      <DataTableColumnHeader column={column} title="Status" />
    ),
    cell: ({ getValue }) => {
      const status = getValue()
      return (
        <Badge variant={statusVariant[status]}>
          <BadgeDot />
          <span className="capitalize">{status}</span>
        </Badge>
      )
    },
  }),
  helper.accessor("method", { header: "Method" }),
  helper.accessor("country", { header: "Country" }),
  helper.accessor("date", {
    header: ({ column }) => (
      <DataTableColumnHeader column={column} title="Date" />
    ),
  }),
  helper.accessor("amount", {
    header: ({ column }) => (
      <DataTableColumnHeader column={column} title="Amount" />
    ),
    cell: ({ getValue }) => currency.format(getValue()),
    meta: { align: "end" },
  }),
])

export function DataTableStickyHeader() {
  const table = useDataTable({
    data: payments,
    columns,
    getRowId: (row) => row.id,
    initialState: { pagination: { pageIndex: 0, pageSize: 100 } },
  })

  return (
    <DataTable table={table}>
      <DataTableToolbar>
        <DataTableSearch placeholder="Search 1,000 payments…" />
        <DataTableViewOptions />
      </DataTableToolbar>
      <DataTableContent stickyHeader containerClassName="max-h-80" />
      <DataTablePagination />
    </DataTable>
  )
}
```

## Props in this example

### DataTable

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `table` | `DataTableInstance<TData>` | – | The table returned by useDataTable. |

### DataTableContent

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `stickyHeader` | `boolean` | `false` | Needs a max height on the container. |
| `containerClassName` | `string` | – | Applied to the scroll container. |

### DataTableSearch

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `placeholder` | `string` | `"Search…"` |  |

### DataTableColumnHeader

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `column` | `Column` | – |  |
| `title` | `string` | – |  |

### DataTableSelectAll

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `table` | `Table` | – |  |

### DataTableSelectRow

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `row` | `Row` | – |  |

## More Data table examples

- [Plain table](https://hextaui.com/docs/data-table/examples/table.md)
- [Loading](https://hextaui.com/docs/data-table/examples/loading.md)
- [Empty](https://hextaui.com/docs/data-table/examples/empty.md)
- [Pinned columns](https://hextaui.com/docs/data-table/examples/pinned-columns.md)
- [Right to left](https://hextaui.com/docs/data-table/examples/rtl.md)
