---
title: Table (React) - USWDS + Tailwind
description: Present data in rows and columns with tables. Compare striped,
  compact, scrollable, and sticky layouts.
---

# Table

Present data in rows and columns with tables. Compare striped, compact, scrollable, and sticky layouts.

## Examples

### Default

```tsx
import { Table } from '@uswds-tailwind/react'

const documents = [
  { title: 'Declaration of Independence', description: 'Statement declaring independence from the British Empire.', year: '1776' },
  { title: 'Bill of Rights', description: 'The first ten amendments of the U.S. Constitution.', year: '1791' },
  { title: 'Declaration of Sentiments', description: 'A declaration of the rights of American women.', year: '1848' },
  { title: 'Emancipation Proclamation', description: 'An executive order granting freedom to enslaved people in designated states.', year: '1863' },
]

export default function TableDemo() {
  return (
    <Table.Root>
      <Table.Caption>Historic documents</Table.Caption>
      <Table.Header>
        <Table.Row>
          <Table.ColumnHeader>Document title</Table.ColumnHeader>
          <Table.ColumnHeader>Description</Table.ColumnHeader>
          <Table.ColumnHeader>Year</Table.ColumnHeader>
        </Table.Row>
      </Table.Header>
      <Table.Body>
        {documents.map(document => (
          <Table.Row key={document.title}>
            <Table.ColumnHeader scope="row" className="font-normal">{document.title}</Table.ColumnHeader>
            <Table.Cell>{document.description}</Table.Cell>
            <Table.Cell>{document.year}</Table.Cell>
          </Table.Row>
        ))}
      </Table.Body>
    </Table.Root>
  )
}
```

### Striped

```tsx
import { Table } from '@uswds-tailwind/react'

const documents = [
  { title: 'Declaration of Independence', description: 'Statement declaring independence from the British Empire.', year: '1776' },
  { title: 'Bill of Rights', description: 'The first ten amendments of the U.S. Constitution.', year: '1791' },
  { title: 'Declaration of Sentiments', description: 'A declaration of the rights of American women.', year: '1848' },
  { title: 'Emancipation Proclamation', description: 'An executive order granting freedom to enslaved people in designated states.', year: '1863' },
]

export default function TableStripedDemo() {
  return (
    <Table.Root variant="striped">
      <Table.Caption>Historic documents with stripes</Table.Caption>
      <Table.Header>
        <Table.Row>
          <Table.ColumnHeader>Document title</Table.ColumnHeader>
          <Table.ColumnHeader>Description</Table.ColumnHeader>
          <Table.ColumnHeader>Year</Table.ColumnHeader>
        </Table.Row>
      </Table.Header>
      <Table.Body>
        {documents.map(document => (
          <Table.Row key={document.title}>
            <Table.ColumnHeader scope="row" className="font-normal">{document.title}</Table.ColumnHeader>
            <Table.Cell>{document.description}</Table.Cell>
            <Table.Cell>{document.year}</Table.Cell>
          </Table.Row>
        ))}
      </Table.Body>
    </Table.Root>
  )
}
```

### Borderless

```tsx
import { Table } from '@uswds-tailwind/react'

const documents = [
  { title: 'Declaration of Independence', description: 'Statement declaring independence from the British Empire.', year: '1776' },
  { title: 'Bill of Rights', description: 'The first ten amendments of the U.S. Constitution.', year: '1791' },
  { title: 'Declaration of Sentiments', description: 'A declaration of the rights of American women.', year: '1848' },
  { title: 'Emancipation Proclamation', description: 'An executive order granting freedom to enslaved people in designated states.', year: '1863' },
]

export default function TableBorderlessDemo() {
  return (
    <Table.Root variant="borderless">
      <Table.Caption>Borderless historic documents</Table.Caption>
      <Table.Header>
        <Table.Row>
          <Table.ColumnHeader>Document title</Table.ColumnHeader>
          <Table.ColumnHeader>Description</Table.ColumnHeader>
          <Table.ColumnHeader>Year</Table.ColumnHeader>
        </Table.Row>
      </Table.Header>
      <Table.Body>
        {documents.map(document => (
          <Table.Row key={document.title}>
            <Table.ColumnHeader scope="row" className="font-normal">{document.title}</Table.ColumnHeader>
            <Table.Cell>{document.description}</Table.Cell>
            <Table.Cell>{document.year}</Table.Cell>
          </Table.Row>
        ))}
      </Table.Body>
    </Table.Root>
  )
}
```

### Compact

```tsx
import { Table } from '@uswds-tailwind/react'

const documents = [
  { title: 'Declaration of Independence', description: 'Statement declaring independence from the British Empire.', year: '1776' },
  { title: 'Bill of Rights', description: 'The first ten amendments of the U.S. Constitution.', year: '1791' },
  { title: 'Declaration of Sentiments', description: 'A declaration of the rights of American women.', year: '1848' },
  { title: 'Emancipation Proclamation', description: 'An executive order granting freedom to enslaved people in designated states.', year: '1863' },
]

export default function TableCompactDemo() {
  return (
    <Table.Root compact>
      <Table.Caption>Compact historic documents</Table.Caption>
      <Table.Header>
        <Table.Row>
          <Table.ColumnHeader>Document title</Table.ColumnHeader>
          <Table.ColumnHeader>Description</Table.ColumnHeader>
          <Table.ColumnHeader>Year</Table.ColumnHeader>
        </Table.Row>
      </Table.Header>
      <Table.Body>
        {documents.map(document => (
          <Table.Row key={document.title}>
            <Table.ColumnHeader scope="row" className="font-normal">{document.title}</Table.ColumnHeader>
            <Table.Cell>{document.description}</Table.Cell>
            <Table.Cell>{document.year}</Table.Cell>
          </Table.Row>
        ))}
      </Table.Body>
    </Table.Root>
  )
}
```

### Scrollable

```tsx
import { Table } from '@uswds-tailwind/react'

const documents = [
  { title: 'Declaration of Independence', description: 'Statement declaring independence from the British Empire.', year: '1776' },
  { title: 'Bill of Rights', description: 'The first ten amendments of the U.S. Constitution.', year: '1791' },
  { title: 'Declaration of Sentiments', description: 'A declaration of the rights of American women.', year: '1848' },
  { title: 'Emancipation Proclamation', description: 'An executive order granting freedom to enslaved people in designated states.', year: '1863' },
]

export default function TableScrollableDemo() {
  return (
    <Table.ScrollArea>
      <Table.Root>
        <Table.Caption>Historic documents</Table.Caption>
        <Table.Header>
          <Table.Row>
            <Table.ColumnHeader>Document title</Table.ColumnHeader>
            <Table.ColumnHeader>Description</Table.ColumnHeader>
            <Table.ColumnHeader>Year</Table.ColumnHeader>
          </Table.Row>
        </Table.Header>
        <Table.Body>
          {documents.map(document => (
            <Table.Row key={document.title}>
              <Table.ColumnHeader scope="row" className="font-normal">{document.title}</Table.ColumnHeader>
              <Table.Cell className="whitespace-nowrap">{document.description}</Table.Cell>
              <Table.Cell>{document.year}</Table.Cell>
            </Table.Row>
          ))}
        </Table.Body>
      </Table.Root>
    </Table.ScrollArea>
  )
}
```

### Sticky Header

```tsx
import { Table } from '@uswds-tailwind/react'

const documents = [
  { title: 'Declaration of Independence', description: 'Statement declaring independence from the British Empire.', year: '1776' },
  { title: 'Bill of Rights', description: 'The first ten amendments of the U.S. Constitution.', year: '1791' },
  { title: 'Declaration of Sentiments', description: 'A declaration of the rights of American women.', year: '1848' },
  { title: 'Emancipation Proclamation', description: 'An executive order granting freedom to enslaved people in designated states.', year: '1863' },
]

export default function TableStickyHeaderDemo() {
  return (
    <Table.ScrollArea className="h-64 overflow-y-auto">
      <Table.Root>
        <Table.Caption>Historic documents with a sticky header</Table.Caption>
        <Table.Header sticky>
          <Table.Row>
            <Table.ColumnHeader>Document title</Table.ColumnHeader>
            <Table.ColumnHeader>Description</Table.ColumnHeader>
            <Table.ColumnHeader>Year</Table.ColumnHeader>
          </Table.Row>
        </Table.Header>
        <Table.Body>
          {[...documents, ...documents].map((document, index) => (
            <Table.Row key={index}>
              <Table.ColumnHeader scope="row" className="font-normal">{document.title}</Table.ColumnHeader>
              <Table.Cell>{document.description}</Table.Cell>
              <Table.Cell>{document.year}</Table.Cell>
            </Table.Row>
          ))}
        </Table.Body>
      </Table.Root>
    </Table.ScrollArea>
  )
}
```

### Sticky Column

```tsx
import { Table } from '@uswds-tailwind/react'

const documents = [
  { title: 'Declaration of Independence', description: 'Statement declaring independence from the British Empire.', year: '1776' },
  { title: 'Bill of Rights', description: 'The first ten amendments of the U.S. Constitution.', year: '1791' },
  { title: 'Declaration of Sentiments', description: 'A declaration of the rights of American women.', year: '1848' },
  { title: 'Emancipation Proclamation', description: 'An executive order granting freedom to enslaved people in designated states.', year: '1863' },
]

export default function TableStickyColumnDemo() {
  return (
    <Table.ScrollArea>
      <Table.Root>
        <Table.Caption>Historic documents with a sticky column</Table.Caption>
        <Table.Header>
          <Table.Row>
            <Table.ColumnHeader className="sticky left-0">Document title</Table.ColumnHeader>
            <Table.ColumnHeader>Description</Table.ColumnHeader>
            <Table.ColumnHeader>Year</Table.ColumnHeader>
          </Table.Row>
        </Table.Header>
        <Table.Body>
          {documents.map(document => (
            <Table.Row key={document.title}>
              <Table.ColumnHeader scope="row" className="font-normal sticky left-0">{document.title}</Table.ColumnHeader>
              <Table.Cell className="whitespace-nowrap">{document.description}</Table.Cell>
              <Table.Cell>{document.year}</Table.Cell>
            </Table.Row>
          ))}
        </Table.Body>
      </Table.Root>
    </Table.ScrollArea>
  )
}
```

### Stacked

```tsx
import { Table } from '@uswds-tailwind/react'

const documents = [
  { title: 'Declaration of Independence', description: 'Statement declaring independence from the British Empire.', year: '1776' },
  { title: 'Bill of Rights', description: 'The first ten amendments of the U.S. Constitution.', year: '1791' },
  { title: 'Declaration of Sentiments', description: 'A declaration of the rights of American women.', year: '1848' },
  { title: 'Emancipation Proclamation', description: 'An executive order granting freedom to enslaved people in designated states.', year: '1863' },
]

export default function TableStackedDemo() {
  return (
    <Table.Root variant="borderless" stacked>
      <Table.Caption>Historic documents</Table.Caption>
      <Table.Header>
        <Table.Row>
          <Table.ColumnHeader>Document title</Table.ColumnHeader>
          <Table.ColumnHeader>Description</Table.ColumnHeader>
          <Table.ColumnHeader>Year</Table.ColumnHeader>
        </Table.Row>
      </Table.Header>
      <Table.Body>
        {documents.map(document => (
          <Table.Row key={document.title}>
            <Table.ColumnHeader scope="row" data-label="Document title" className="font-normal">{document.title}</Table.ColumnHeader>
            <Table.Cell data-label="Description">{document.description}</Table.Cell>
            <Table.Cell data-label="Year">{document.year}</Table.Cell>
          </Table.Row>
        ))}
      </Table.Body>
    </Table.Root>
  )
}
```

On narrow screens, `stacked` hides the visual header row and places each cell on its own line. Keep the native caption and column headers, and add a `data-label` that matches its column heading to every body cell.

### Sortable

```tsx
import type { ColumnDef } from '@tanstack/react-table'
import {
  createSortedRowModel,
  rowSortingFeature,
  sortFn_basic,
  sortFn_text,
  tableFeatures,
  useTable,
} from '@tanstack/react-table'
import { Table } from '@uswds-tailwind/react'

interface Agency {
  name: string
  employees: number
  lastUpdated: string
}

const agencies: Agency[] = [
  { name: 'National Park Service', employees: 20248, lastUpdated: '2025-06-12' },
  { name: 'Census Bureau', employees: 10632, lastUpdated: '2025-03-28' },
  { name: 'National Archives', employees: 2915, lastUpdated: '2025-08-04' },
  { name: 'Smithsonian Institution', employees: 6547, lastUpdated: '2025-01-17' },
]

const dateFormatter = new Intl.DateTimeFormat('en-US', { dateStyle: 'medium', timeZone: 'UTC' })

const features = tableFeatures({
  rowSortingFeature,
  sortedRowModel: createSortedRowModel(),
  sortFns: { text: sortFn_text, basic: sortFn_basic },
})

const columns: ColumnDef<typeof features, Agency>[] = [
  { accessorKey: 'name', header: 'Agency', sortFn: 'text' },
  { accessorKey: 'employees', header: 'Employees', sortFn: 'basic' },
  { accessorKey: 'lastUpdated', header: 'Last updated', sortFn: 'basic' },
]

export default function TableSortedDemo() {
  const dataTable = useTable({ features, columns, data: agencies })
  const sorting = dataTable.state.sorting[0]
  const sortColumn = sorting
    ? ({ name: 0, employees: 1, lastUpdated: 2 } as Record<string, number>)[sorting.id] ?? null
    : null

  return (
    <Table.Root
      captionText="Federal agency employees"
      columnNames={{ 0: 'Agency', 1: 'Employees', 2: 'Last updated' }}
      sortDescriptor={sorting && sortColumn !== null
        ? { column: sortColumn, direction: sorting.desc ? 'descending' : 'ascending' }
        : null}
      onSortChange={({ sortDescriptor }) => {
        dataTable.setSorting(sortDescriptor === null
          ? []
          : [{ id: sortDescriptor.column === 0 ? 'name' : sortDescriptor.column === 1 ? 'employees' : 'lastUpdated', desc: sortDescriptor.direction === 'descending' }])
      }}
    >
      <Table.Caption>Federal agency employees</Table.Caption>
      <Table.Header>
        <Table.Row>
          <Table.ColumnHeader columnIndex={0} sortable>Agency</Table.ColumnHeader>
          <Table.ColumnHeader columnIndex={1} sortable>Employees</Table.ColumnHeader>
          <Table.ColumnHeader columnIndex={2} sortable>Last updated</Table.ColumnHeader>
        </Table.Row>
      </Table.Header>
      <Table.Body>
        {dataTable.getRowModel().rows.map(row => (
          <Table.Row key={row.id}>
            <Table.ColumnHeader scope="row">{row.original.name}</Table.ColumnHeader>
            <Table.Cell columnIndex={1}>{row.original.employees.toLocaleString()}</Table.Cell>
            <Table.Cell columnIndex={2}>{dateFormatter.format(new Date(`${row.original.lastUpdated}T00:00:00Z`))}</Table.Cell>
          </Table.Row>
        ))}
      </Table.Body>
    </Table.Root>
  )
}
```

Install `@tanstack/react-table` to sort the data. `Table.Root` supplies sort buttons, `aria-sort`, and a status announcement; `onSortChange` forwards the requested column and direction to TanStack Table, whose row model supplies the reordered rows. Keep the Table sort state and row order in sync so the announcement describes the visible data.

## Component API

Compose native table parts inside Root. Use column indices for machine-driven sorting and stacked cell labels.

### Root `<table>`

Renders a div around the native table and an automatic sort status region. Native table props apply to the inner table.

| Prop | Type | Default | Description |
| - | - | - | - |
| `variant` | `bordered \| striped \| borderless` | `bordered` | Selects border and row styling. |
| `compact` | `boolean` | `false` | Reduces cell padding. |
| `stacked` | `boolean` | `false` | Stacks cells on narrow screens. |
| `captionText` | `string` | `—` | Caption text for table sorting status. |
| `columnNames` | `Record<number, string>` | `—` | Accessible names for column indices. Every sortable ColumnHeader requires a nonempty matching entry. |
| `defaultSortDescriptor` | `{ column: number; direction: 'ascending' \| 'descending' } \| null` | `—` | Initial uncontrolled sort state. |
| `sortDescriptor` | `{ column: number; direction: 'ascending' \| 'descending' } \| null` | `—` | Controlled sort state. Use null for an unsorted table and reorder your rows to match. |
| `onSortChange` | `({ sortDescriptor: SortDescriptor \| null }) => void` | `—` | Receives sorting requests. Update sortDescriptor and the row order together. |

### Caption `<caption>`

Names the table.

### Header `<thead>`

Contains column headings.

| Prop | Type | Description |
| - | - | - |
| `sticky` | `boolean` | Keeps the header visible while scrolling. |

### Body `<tbody>`

Contains data rows.

### Footer `<tfoot>`

Contains footer rows.

### Row `<tr>`

Contains cells.

### ColumnHeader `<th>`

Labels a column.

| Prop | Type | Description |
| - | - | - |
| `columnIndex` | `number` | Connects this heading to the sorting machine. |
| `sortable` | `boolean` | Renders a sort button when columnIndex is supplied. |

| Attribute | Value | Purpose |
| - | - | - |
| `data-sortable` | `Present when sortable` | Style sortable column headings. |

### Cell `<td>`

Displays one value. Supply data-label for a stacked cell heading.

| Prop | Type | Description |
| - | - | - |
| `columnIndex` | `number` | Connects this cell to the active sorted column. |

| Attribute | Value | Purpose |
| - | - | - |
| `data-sort-active` | `Present for cells in the sorted column` | Styles the active sorted column. |

### ScrollArea `<div>`

Wraps the table in a horizontally scrollable region.

### SrStatus `<div>`

Announces sort changes. Root renders this part automatically; do not add a second status region.

### Accessibility

Provide a caption and column headings. Handle onSortChange in the data source so sorted rows match the announced sort state.
