---
title: Table
description: A responsive table component.
icon: table
author:
  name: shadcn
  url: https://github.com/shadcn
related:
  - /data-table
  - /data-table-filter
mode: wide
---

<PoweredBy packages={[{ name: 'Shadcn UI', url: 'https://ui.shadcn.com/docs/components/table' }]} />

### Installation

```package-install
npx shadcn@latest add @redpanda/table
```

<ComponentPreview name="table-demo" />

## When to use

Tables display structured data in rows and columns. Use this decision tree:

```mermaid
graph TD;
    A[Displaying tabular data?] --> B{Interactive features needed?};
    A --> |No| C[Use other layout components];
    B --> |Yes| D[Use Table Component];
    B --> |No| E{Simple data display?};
    D --> F{What complexity level?};
    E --> |Yes| G[Basic Table];
    F --> H[Data Table with sorting/filtering];
    F --> I[Simple responsive Table];

    classDef recommended fill:#4ade80,stroke:#22c55e,stroke-width:2px,color:#000;
    classDef discouraged fill:#fbbf24,stroke:#f59e0b,stroke-width:2px,color:#000;
    classDef decision fill:#60a5fa,stroke:#3b82f6,stroke-width:2px,color:#fff;

    class D,G,H,I recommended;
    class C discouraged;
    class A,B,E,F decision;
```

## Usage

```tsx
import { Table, TableBody, TableCaption, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
```

```tsx
<Table>
  <TableCaption>A list of your recent invoices.</TableCaption>
  <TableHeader>
    <TableRow>
      <TableHead width="sm">Invoice</TableHead>
      <TableHead>Status</TableHead>
      <TableHead>Method</TableHead>
      <TableHead align="right">Amount</TableHead>
    </TableRow>
  </TableHeader>
  <TableBody>
    <TableRow>
      <TableCell weight="medium">INV001</TableCell>
      <TableCell>Paid</TableCell>
      <TableCell>Credit Card</TableCell>
      <TableCell align="right">$250.00</TableCell>
    </TableRow>
  </TableBody>
</Table>
```

Use `TableCell numeric` for numeric values that need equal-width figures. Combine it with
`align="right"` for a numeric column. `numeric` does not parse or format values: use
`Intl.NumberFormat` for currency, units, or locale-aware separators.

### Cell appearance

Use component props instead of overriding cell typography, color, or padding:

| Prop       | Values                                                | Default behavior                                                                     |
| ---------- | ----------------------------------------------------- | ------------------------------------------------------------------------------------ |
| `density`  | `comfortable`, `standard`, `compact`, `dense`, `none` | `comfortable` retains `p-4`. Other densities use `py-2`, `py-1.5`, `py-1`, or `p-0`. |
| `font`     | `body`, `mono`                                        | Inherits the table font. `mono` uses `font-mono`.                                    |
| `size`     | `sm`, `md`, `lg`                                      | Inherits the table size when omitted.                                                |
| `tone`     | `default`, `muted`, `success`, `destructive`          | Inherits the text color. `muted` uses the subtle text token.                         |
| `weight`   | `normal`, `medium`, `semibold`, `bold`                | `normal`                                                                             |
| `align`    | `left`, `center`, `right`                             | `left`                                                                               |
| `truncate` | `none`, `truncate`                                    | `none`. Truncation also sets `max-w-0` to constrain the cell.                        |

Use `TableHead variant="compact"` for the shorter, uppercase header treatment. Its default header treatment remains unchanged.

```tsx
<TableHead variant="compact">Source</TableHead>
<TableCell density="compact" font="mono" size="sm">broker-0</TableCell>
<TableCell align="right" density="compact" numeric tone="success">42</TableCell>
```

### Stripes, pinned rows and detail cells

Use `Table variant="striped"` for alternating rendered body rows. The table owns the recessed stripe and matching pinned-column wash; hover, expanded and selected states retain their own feedback. Do not paint individual rows from their array index.

`TableRow pinned="top"` or `pinned="bottom"` marks a row that the caller has already ordered into that section. It owns the pinned row's recessed rest treatment; it does not sort data or add sticky positioning. This is distinct from horizontal boundary-column pinning below.

Use `TableCell variant="detail"` for an expanded row's full-width detail content. Keep `colSpan` equal to the actual visible column count and choose the existing density contract for its padding:

```tsx
<TableRow>
  <TableCell colSpan={4} density="comfortable" variant="detail">
    Topic configuration
  </TableCell>
</TableRow>
```

### Pinned boundary columns

Use `pinned="start"` or `pinned="end"` on matching `TableHead` and `TableCell` elements. These are logical edges, so they follow the table's writing direction. Pin at most one boundary column per edge: this API does not calculate offsets for multiple pinned columns.

```tsx
<Table className="min-w-200">
  <TableHeader>
    <TableRow>
      <TableHead pinned="start">Cluster</TableHead>
      <TableHead>Region</TableHead>
      <TableHead align="right" pinned="end">
        Monthly cost
      </TableHead>
    </TableRow>
  </TableHeader>
  <TableBody>
    <TableRow>
      <TableCell pinned="start" weight="medium">
        prod-east
      </TableCell>
      <TableCell>us-east-1</TableCell>
      <TableCell align="right" numeric pinned="end">
        12,400
      </TableCell>
    </TableRow>
  </TableBody>
</Table>
```

The component owns sticky positioning, the boundary separator, and an opaque card surface so scrolled content cannot show through. Pinned body cells also reflect the row's hover, expanded, and `data-state="selected"` washes; matching headers sit above body cells. Pinned cells retain that opaque card surface even with `Table variant="simple"`; they are not transparent overlays. Horizontal pinning does not make the header vertically sticky.

### Sticky header and keyboard scrolling

Set `stickyHeader` on `Table` inside a parent with an explicit height. The component fills that parent and owns the single horizontal and vertical scroll viewport, opaque header surface, and header stacking. Do not put a second scrolling wrapper around it or style `TableHeader` as sticky yourself.

```tsx
<div className="h-90">
  <Table aria-label="Broker metrics" stickyHeader>
    <TableHeader>{/* Column headings */}</TableHeader>
    <TableBody>{/* Rows */}</TableBody>
  </Table>
</div>
```

`onViewportScroll` receives `React.UIEvent<HTMLElement>` from the actual viewport. Read `event.currentTarget.scrollTop`, `scrollHeight`, and `clientHeight` there for windowed rendering or incremental loading. Native `onScroll`, `ref`, `className`, and other table attributes still belong to the underlying `<table>`; they are not moved to the viewport.

The viewport is a keyboard-focusable named region. Supply the table's `aria-label` or `aria-labelledby` for a meaningful shared name; otherwise the region is called “Scrollable table.” Keyboard users can focus it and use native scrolling keys. A focused activatable `TableRow` owns its inset three-pixel focus ring; the primitive does not make ordinary rows interactive or add tab stops to them.

`stickyHeader` is opt-in and does not change unbounded tables. It can be combined with a single `pinned` boundary column per edge; the header remains above pinned body cells.

## Data Table

You can use the `<Table />` component to build more complex data tables. Combine it with the installed TanStack Table v9 adapter to create tables with sorting, filtering and pagination.

See the [Data Table](/docs/data-table) documentation for more information.

## Dropzone Table

You can combine the `<Table />` component with file upload functionality to create a table-based file upload interface. This example shows how to display uploaded files in a table format with file icons, progress indicators, and action buttons.

<ComponentPreview name="dropzone-table-demo" />

## Variants

### Striped rows

<ComponentPreview name="table-striped-demo" description="Table with alternating row backgrounds for dense datasets." />

### Bordered

<ComponentPreview name="table-bordered-demo" description="Table with a full-border container style." />

### Compact

<ComponentPreview name="table-compact-demo" description="Table with a compact row height for admin surfaces." />

### Caption

<ComponentPreview name="table-caption-demo" description="Table with a visible caption providing accessible context." />

### Footer totals

<ComponentPreview
  name="table-footer-demo"
  description="Table with a tfoot row aggregating totals for numerical columns."
/>

## Credits

- We take our inspiration from [Shadcn UI](https://ui.shadcn.com/docs/components/table) for the table component and style.
