Chuyển tới nội dung
Component

Table

Rows of data that compare along columns: members, invoices, deployments. Rows run edge to edge with a divider under each, header text is secondary, and a body row takes the neutral hover overlay. Cells hold any component: an Avatar and a name, a Badge, a Checkbox to pick the row, a DropdownMenu of row actions. Headers can sort, a TableToolbar above and below holds search, actions, the count and Pagination, and TableEmpty says when there are no rows. TableSelection adds row picking: a checkbox per row, one in the header that follows them, and a bar that counts and names the picked rows.

Xem trước

Invoices for 2026.
StatusAmountActions
INV-001Paid$250.00
INV-002Due$1,200.00

Props

  • align
  • numeric
  • fit
  • sortDirection
  • selected
  • defaultValue
  • value
  • label
  • max
  • pending
  • pendingLabel
import { IconDots } from "@tabler/icons-react"
import { Button } from "@/components/ui/button"
import { Table, TableBody, TableCaption, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"

<Table className="max-w-lg">
  <TableCaption>Invoices for 2026.</TableCaption>
  <TableHeader>
    <TableRow>
      <TableHead onSort={sortBy}>Invoice</TableHead>
      <TableHead>Status</TableHead>
      <TableHead numeric>Amount</TableHead>
      <TableHead>
        <span className="sr-only">Actions</span>
      </TableHead>
    </TableRow>
  </TableHeader>
  <TableBody>
    <TableRow>
      <TableCell>INV-001</TableCell>
      <TableCell>Paid</TableCell>
      <TableCell numeric>$250.00</TableCell>
      <TableCell>
        <Button variant="ghost" size="sm" iconOnly aria-label="Actions for INV-001">
          <IconDots size={16} weight="regular" />
        </Button>
      </TableCell>
    </TableRow>
    <TableRow>
      <TableCell>INV-002</TableCell>
      <TableCell>Due</TableCell>
      <TableCell numeric>$1,200.00</TableCell>
      <TableCell>
        <Button variant="ghost" size="sm" iconOnly aria-label="Actions for INV-002">
          <IconDots size={16} weight="regular" />
        </Button>
      </TableCell>
    </TableRow>
  </TableBody>
</Table>

Cách dùng

Khi nào dùng

  • Rows of data the user compares along columns: members, invoices, deployments.
  • Numbers that line up down a column, such as amounts or counts.
  • Rows the user picks and then acts on together, such as removing several members, with TableSelection.

Khi nào không dùng

  • A single list of items with one or two lines each. Use a list of rows instead.
  • A grid of cards, such as projects with images. Use Card.
  • Layout. Use flex or grid classes.

Biến thể

align

Set on TableHead and TableCell. Where the content sits in the cell. end for numbers, center only for short status marks.

  • Invoices for 2026.
    StatusAmountActions
    INV-001Paid$250.00
    INV-002Due$1,200.00

    startmặc định

  • Invoices for 2026.
    StatusAmountActions
    INV-001Paid$250.00
    INV-002Due$1,200.00

    center

  • Invoices for 2026.
    StatusAmountActions
    INV-001Paid$250.00
    INV-002Due$1,200.00

    end

import { IconDots } from "@tabler/icons-react"
import { Button } from "@/components/ui/button"
import { Table, TableBody, TableCaption, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"

<Table className="max-w-lg">
  <TableCaption>Invoices for 2026.</TableCaption>
  <TableHeader>
    <TableRow>
      <TableHead onSort={sortBy}>Invoice</TableHead>
      <TableHead>Status</TableHead>
      <TableHead numeric>Amount</TableHead>
      <TableHead>
        <span className="sr-only">Actions</span>
      </TableHead>
    </TableRow>
  </TableHeader>
  <TableBody>
    <TableRow>
      <TableCell>INV-001</TableCell>
      <TableCell>Paid</TableCell>
      <TableCell numeric>$250.00</TableCell>
      <TableCell>
        <Button variant="ghost" size="sm" iconOnly aria-label="Actions for INV-001">
          <IconDots size={16} weight="regular" />
        </Button>
      </TableCell>
    </TableRow>
    <TableRow>
      <TableCell>INV-002</TableCell>
      <TableCell>Due</TableCell>
      <TableCell numeric>$1,200.00</TableCell>
      <TableCell>
        <Button variant="ghost" size="sm" iconOnly aria-label="Actions for INV-002">
          <IconDots size={16} weight="regular" />
        </Button>
      </TableCell>
    </TableRow>
  </TableBody>
</Table>
<Table className="max-w-lg">
  <TableCaption>Invoices for 2026.</TableCaption>
  <TableHeader>
    <TableRow>
      <TableHead onSort={sortBy}>Invoice</TableHead>
      <TableHead align="center">Status</TableHead>
      <TableHead numeric>Amount</TableHead>
      <TableHead>
        <span className="sr-only">Actions</span>
      </TableHead>
    </TableRow>
  </TableHeader>
  <TableBody>
    <TableRow>
      <TableCell>INV-001</TableCell>
      <TableCell align="center">Paid</TableCell>
      <TableCell numeric>$250.00</TableCell>
      <TableCell>
        <Button variant="ghost" size="sm" iconOnly aria-label="Actions for INV-001">
          <IconDots size={16} weight="regular" />
        </Button>
      </TableCell>
    </TableRow>
    <TableRow>
      <TableCell>INV-002</TableCell>
      <TableCell align="center">Due</TableCell>
      <TableCell numeric>$1,200.00</TableCell>
      <TableCell>
        <Button variant="ghost" size="sm" iconOnly aria-label="Actions for INV-002">
          <IconDots size={16} weight="regular" />
        </Button>
      </TableCell>
    </TableRow>
  </TableBody>
</Table>
<Table className="max-w-lg">
  <TableCaption>Invoices for 2026.</TableCaption>
  <TableHeader>
    <TableRow>
      <TableHead onSort={sortBy}>Invoice</TableHead>
      <TableHead align="end">Status</TableHead>
      <TableHead numeric>Amount</TableHead>
      <TableHead>
        <span className="sr-only">Actions</span>
      </TableHead>
    </TableRow>
  </TableHeader>
  <TableBody>
    <TableRow>
      <TableCell>INV-001</TableCell>
      <TableCell align="end">Paid</TableCell>
      <TableCell numeric>$250.00</TableCell>
      <TableCell>
        <Button variant="ghost" size="sm" iconOnly aria-label="Actions for INV-001">
          <IconDots size={16} weight="regular" />
        </Button>
      </TableCell>
    </TableRow>
    <TableRow>
      <TableCell>INV-002</TableCell>
      <TableCell align="end">Due</TableCell>
      <TableCell numeric>$1,200.00</TableCell>
      <TableCell>
        <Button variant="ghost" size="sm" iconOnly aria-label="Actions for INV-002">
          <IconDots size={16} weight="regular" />
        </Button>
      </TableCell>
    </TableRow>
  </TableBody>
</Table>

sortDirection

Set on a sortable TableHead. How the table is sorted by this column; sets aria-sort and the caret.

  • Invoices for 2026.
    StatusAmountActions
    INV-001Paid$250.00
    INV-002Due$1,200.00

    nonemặc định

  • Invoices for 2026.
    StatusAmountActions
    INV-001Paid$250.00
    INV-002Due$1,200.00

    ascending

  • Invoices for 2026.
    StatusAmountActions
    INV-001Paid$250.00
    INV-002Due$1,200.00

    descending

import { IconDots } from "@tabler/icons-react"
import { Button } from "@/components/ui/button"
import { Table, TableBody, TableCaption, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"

<Table className="max-w-lg">
  <TableCaption>Invoices for 2026.</TableCaption>
  <TableHeader>
    <TableRow>
      <TableHead onSort={sortBy}>Invoice</TableHead>
      <TableHead>Status</TableHead>
      <TableHead numeric>Amount</TableHead>
      <TableHead>
        <span className="sr-only">Actions</span>
      </TableHead>
    </TableRow>
  </TableHeader>
  <TableBody>
    <TableRow>
      <TableCell>INV-001</TableCell>
      <TableCell>Paid</TableCell>
      <TableCell numeric>$250.00</TableCell>
      <TableCell>
        <Button variant="ghost" size="sm" iconOnly aria-label="Actions for INV-001">
          <IconDots size={16} weight="regular" />
        </Button>
      </TableCell>
    </TableRow>
    <TableRow>
      <TableCell>INV-002</TableCell>
      <TableCell>Due</TableCell>
      <TableCell numeric>$1,200.00</TableCell>
      <TableCell>
        <Button variant="ghost" size="sm" iconOnly aria-label="Actions for INV-002">
          <IconDots size={16} weight="regular" />
        </Button>
      </TableCell>
    </TableRow>
  </TableBody>
</Table>
<Table className="max-w-lg">
  <TableCaption>Invoices for 2026.</TableCaption>
  <TableHeader>
    <TableRow>
      <TableHead sortDirection="ascending" onSort={sortBy}>Invoice</TableHead>
      <TableHead>Status</TableHead>
      <TableHead numeric>Amount</TableHead>
      <TableHead>
        <span className="sr-only">Actions</span>
      </TableHead>
    </TableRow>
  </TableHeader>
  <TableBody>
    <TableRow>
      <TableCell>INV-001</TableCell>
      <TableCell>Paid</TableCell>
      <TableCell numeric>$250.00</TableCell>
      <TableCell>
        <Button variant="ghost" size="sm" iconOnly aria-label="Actions for INV-001">
          <IconDots size={16} weight="regular" />
        </Button>
      </TableCell>
    </TableRow>
    <TableRow>
      <TableCell>INV-002</TableCell>
      <TableCell>Due</TableCell>
      <TableCell numeric>$1,200.00</TableCell>
      <TableCell>
        <Button variant="ghost" size="sm" iconOnly aria-label="Actions for INV-002">
          <IconDots size={16} weight="regular" />
        </Button>
      </TableCell>
    </TableRow>
  </TableBody>
</Table>
<Table className="max-w-lg">
  <TableCaption>Invoices for 2026.</TableCaption>
  <TableHeader>
    <TableRow>
      <TableHead sortDirection="descending" onSort={sortBy}>Invoice</TableHead>
      <TableHead>Status</TableHead>
      <TableHead numeric>Amount</TableHead>
      <TableHead>
        <span className="sr-only">Actions</span>
      </TableHead>
    </TableRow>
  </TableHeader>
  <TableBody>
    <TableRow>
      <TableCell>INV-001</TableCell>
      <TableCell>Paid</TableCell>
      <TableCell numeric>$250.00</TableCell>
      <TableCell>
        <Button variant="ghost" size="sm" iconOnly aria-label="Actions for INV-001">
          <IconDots size={16} weight="regular" />
        </Button>
      </TableCell>
    </TableRow>
    <TableRow>
      <TableCell>INV-002</TableCell>
      <TableCell>Due</TableCell>
      <TableCell numeric>$1,200.00</TableCell>
      <TableCell>
        <Button variant="ghost" size="sm" iconOnly aria-label="Actions for INV-002">
          <IconDots size={16} weight="regular" />
        </Button>
      </TableCell>
    </TableRow>
  </TableBody>
</Table>

Ví dụ

Invoices

Invoices for 2026.
InvoiceStatusAmount
INV-001Paid$250.00
INV-002Due$1,200.00
Total$1,450.00
import { Badge } from "@/components/ui/badge"
import { Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow } from "@/components/ui/table"

<Table>
  <TableCaption>Invoices for 2026.</TableCaption>
  <TableHeader>
    <TableRow>
      <TableHead>Invoice</TableHead>
      <TableHead>Status</TableHead>
      <TableHead numeric>Amount</TableHead>
    </TableRow>
  </TableHeader>
  <TableBody>
    <TableRow>
      <TableCell>INV-001</TableCell>
      <TableCell>
        <Badge variant="success">Paid</Badge>
      </TableCell>
      <TableCell numeric>$250.00</TableCell>
    </TableRow>
    <TableRow>
      <TableCell>INV-002</TableCell>
      <TableCell>
        <Badge variant="warning">Due</Badge>
      </TableCell>
      <TableCell numeric>$1,200.00</TableCell>
    </TableRow>
  </TableBody>
  <TableFooter>
    <TableRow>
      <TableCell>Total</TableCell>
      <TableCell />
      <TableCell numeric>$1,450.00</TableCell>
    </TableRow>
  </TableFooter>
</Table>

Members

Rows picked with TableSelection: the header checkbox follows the rows, and the bar above counts and names the picked ones, with the actions on them. Search and an action above the table, a sortable name, an Avatar and an email in one cell, a Badge, row actions at the end, the count with Pagination below.

RoleActions
Ada Lovelaceada@example.com
Owner
Grace Hoppergrace@example.com
Editor
2 of 24 members

import { IconDots, IconSearch } from "@tabler/icons-react"
import { Avatar } from "@/components/ui/avatar"
import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"
import { Input } from "@/components/ui/input"
import { Pagination } from "@/components/ui/pagination"
import { Table, TableActions, TableBody, TableCell, TableHead, TableHeader, TableRow, TableToolbar } from "@/components/ui/table"
import { TableRowSelect, TableSelectAll, TableSelection, TableSelectionBar } from "@/components/ui/table-selection"

<TableSelection defaultValue={["ada"]}>
  <TableSelectionBar>
    <Button variant="secondary" size="sm">Export</Button>
    <Button variant="destructive-soft" size="sm">Remove</Button>
  </TableSelectionBar>
  <TableToolbar>
    <Input aria-label="Search members" placeholder="Search members" leadingIcon={<IconSearch size={16} />} className="max-w-xs" />
    <TableActions>
      <Button>Invite</Button>
    </TableActions>
  </TableToolbar>
  <Table aria-label="Members">
    <TableHeader>
      <TableRow>
        <TableHead fit>
          <TableSelectAll aria-label="Select all members" />
        </TableHead>
        <TableHead sortDirection="ascending" onSort={sortBy}>Name</TableHead>
        <TableHead>Role</TableHead>
        <TableHead fit>
          <span className="sr-only">Actions</span>
        </TableHead>
      </TableRow>
    </TableHeader>
    <TableBody>
      <TableRow>
        <TableCell fit>
          <TableRowSelect value="ada" label="Ada Lovelace" />
        </TableCell>
        <TableCell>
          <div className="flex items-center gap-sm">
            <Avatar alt="" color="blue">AL</Avatar>
            <div className="flex flex-col">
              <span className="font-medium">Ada Lovelace</span>
              <span className="text-sm text-text-secondary">ada@example.com</span>
            </div>
          </div>
        </TableCell>
        <TableCell>
          <Badge>Owner</Badge>
        </TableCell>
        <TableCell fit>
          <DropdownMenu>
            <DropdownMenuTrigger asChild>
              <Button variant="ghost" size="sm" iconOnly aria-label="Actions for Ada Lovelace">
                <IconDots size={16} />
              </Button>
            </DropdownMenuTrigger>
            <DropdownMenuContent align="end">
              <DropdownMenuItem>Change role</DropdownMenuItem>
              <DropdownMenuItem destructive>Remove from team</DropdownMenuItem>
            </DropdownMenuContent>
          </DropdownMenu>
        </TableCell>
      </TableRow>
      <TableRow>
        <TableCell fit>
          <TableRowSelect value="grace" label="Grace Hopper" />
        </TableCell>
        <TableCell>
          <div className="flex items-center gap-sm">
            <Avatar alt="" color="green">GH</Avatar>
            <div className="flex flex-col">
              <span className="font-medium">Grace Hopper</span>
              <span className="text-sm text-text-secondary">grace@example.com</span>
            </div>
          </div>
        </TableCell>
        <TableCell>
          <Badge variant="outline">Editor</Badge>
        </TableCell>
        <TableCell fit>
          <DropdownMenu>
            <DropdownMenuTrigger asChild>
              <Button variant="ghost" size="sm" iconOnly aria-label="Actions for Grace Hopper">
                <IconDots size={16} />
              </Button>
            </DropdownMenuTrigger>
            <DropdownMenuContent align="end">
              <DropdownMenuItem>Change role</DropdownMenuItem>
              <DropdownMenuItem destructive>Remove from team</DropdownMenuItem>
            </DropdownMenuContent>
          </DropdownMenu>
        </TableCell>
      </TableRow>
    </TableBody>
  </Table>
  <TableToolbar>
    <span className="text-sm text-text-secondary">2 of 24 members</span>
    <TableActions>
      <Pagination page={1} pageCount={12} onPageChange={goToPage} />
    </TableActions>
  </TableToolbar>
</TableSelection>

Many rows picked

Past three names the bar shows the rest as a count. max on TableSelectionBar changes how many names show.

NameRole
Ada LovelaceOwner
Grace HopperEditor
Alan TuringEditor
Katherine JohnsonViewer
Linus TorvaldsViewer
Margaret HamiltonViewer

import { Button } from "@/components/ui/button"
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
import { TableRowSelect, TableSelectAll, TableSelection, TableSelectionBar } from "@/components/ui/table-selection"

<TableSelection defaultValue={["ada", "grace", "alan", "katherine", "linus"]}>
  <TableSelectionBar>
    <Button variant="destructive-soft" size="sm">Remove</Button>
  </TableSelectionBar>
  <Table aria-label="Members">
    <TableHeader>
      <TableRow>
        <TableHead fit>
          <TableSelectAll aria-label="Select all members" />
        </TableHead>
        <TableHead>Name</TableHead>
        <TableHead>Role</TableHead>
      </TableRow>
    </TableHeader>
    <TableBody>
      <TableRow>
        <TableCell fit>
          <TableRowSelect value="ada" label="Ada Lovelace" />
        </TableCell>
        <TableCell>Ada Lovelace</TableCell>
        <TableCell>Owner</TableCell>
      </TableRow>
      <TableRow>
        <TableCell fit>
          <TableRowSelect value="grace" label="Grace Hopper" />
        </TableCell>
        <TableCell>Grace Hopper</TableCell>
        <TableCell>Editor</TableCell>
      </TableRow>
      <TableRow>
        <TableCell fit>
          <TableRowSelect value="alan" label="Alan Turing" />
        </TableCell>
        <TableCell>Alan Turing</TableCell>
        <TableCell>Editor</TableCell>
      </TableRow>
      <TableRow>
        <TableCell fit>
          <TableRowSelect value="katherine" label="Katherine Johnson" />
        </TableCell>
        <TableCell>Katherine Johnson</TableCell>
        <TableCell>Viewer</TableCell>
      </TableRow>
      <TableRow>
        <TableCell fit>
          <TableRowSelect value="linus" label="Linus Torvalds" />
        </TableCell>
        <TableCell>Linus Torvalds</TableCell>
        <TableCell>Viewer</TableCell>
      </TableRow>
      <TableRow>
        <TableCell fit>
          <TableRowSelect value="margaret" label="Margaret Hamilton" />
        </TableCell>
        <TableCell>Margaret Hamilton</TableCell>
        <TableCell>Viewer</TableCell>
      </TableRow>
    </TableBody>
  </Table>
</TableSelection>

No results

NameRole
No members match your search.
import { Table, TableBody, TableEmpty, TableHead, TableHeader, TableRow } from "@/components/ui/table"

<Table aria-label="Members">
  <TableHeader>
    <TableRow>
      <TableHead>Name</TableHead>
      <TableHead>Role</TableHead>
    </TableRow>
  </TableHeader>
  <TableBody>
    <TableEmpty colSpan={2}>No members match your search.</TableEmpty>
  </TableBody>
</Table>

Next page loading

The user asked for page 2, which is still loading: page 1 stays, dimmed and marked busy. In an app, pending is the isPending of the transition that changes the page.

Loading page 2
InvoiceAmount
INV-001$250.00
INV-002$150.00
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"

<Table pending pendingLabel="Loading page 2" aria-label="Invoices">
  <TableHeader>
    <TableRow>
      <TableHead>Invoice</TableHead>
      <TableHead numeric>Amount</TableHead>
    </TableRow>
  </TableHeader>
  <TableBody>
    <TableRow>
      <TableCell>INV-001</TableCell>
      <TableCell numeric>$250.00</TableCell>
    </TableRow>
    <TableRow>
      <TableCell>INV-002</TableCell>
      <TableCell numeric>$150.00</TableCell>
    </TableRow>
  </TableBody>
</Table>

Nên và không nên

Nên

  • Use numeric on every cell of a number column, the header too.
  • Put the table on a surface with its own edge (a Card, a page section); the table has none.
  • Name it with a caption, or aria-label when a heading above says what it is.
  • Put search and the main action in a TableToolbar above the table, and the count with Pagination in one below.
  • Use fit for a Checkbox column and an actions column, so the data columns get the width.
  • For rows the user picks and then acts on together, wrap the table in TableSelection; it keeps the header checkbox, the rows and the bar in step.
  • Put the actions on the picked rows (Export, Delete) in TableSelectionBar, as sm buttons.
  • When a page, sort or filter loads from the server, change it in a transition (useTransition) and pass isPending as pending: the current rows stay until the next ones arrive. Use Skeleton rows only for the first load.

Không nên

  • Don't wrap each row in a Card; rows are the layout.
  • Don't center text columns; start-align them.
  • Don't use a table for layout or for a single list of items.
  • Don't wire Checkbox cells by hand when TableSelection fits; the header checkbox and the bar drift out of step.
  • Don't empty the table or swap the rows for a spinner while the next page loads; the layout jumps and the user loses their place.

Props

TênKiểuGiá trịMặc địnhMô tả
alignenumstart | center | endstartSet on TableHead and TableCell. Where the content sits in the cell. end for numbers, center only for short status marks.
numericboolean-falseSet on TableHead and TableCell. A number column: end-aligned, mono, tabular figures, so digits line up.
fitboolean-falseSet on TableHead and TableCell. The column takes only the room its content needs: a Checkbox, a row's actions.
sortDirectionenumnone | ascending | descendingnoneSet on a sortable TableHead. How the table is sorted by this column; sets aria-sort and the caret.
onSortfunction--Set on TableHead. Makes the header a button that sorts by the column. Sort the rows yourself, then set sortDirection.
selectedboolean-falseSet on TableRow. The row is picked with its Checkbox: the muted accent fill.
colSpannumber--Set on TableEmpty. How many columns the table has, so the row spans them all.
defaultValuestring--Set on TableSelection. Values of the rows picked on first render, as an array: ["ada"]. Each value is a TableRowSelect's value.
valuestring--Set on TableSelection: the picked values as an array, when you control them. Pair it with onValueChange. TableRowSelect takes one value, its row's id.
onValueChangefunction--Set on TableSelection. Called with the new array of picked values.
labelstring--Set on TableRowSelect, with its value. The row's name: TableSelectionBar shows it, and it names the checkbox "Select <label>".
maxnumber-3Set on TableSelectionBar. How many names show before the rest becomes +N.
pendingboolean-falseTable: the next page, sort or filter is still loading. Change it in a transition (useTransition) so the current rows stay, and pass its isPending: the rows dim to opacity-pending and the table is marked aria-busy instead of going blank.
pendingLabelstring-LoadingTable: read by screen readers in a polite status while pending.

Slot

TênBắt buộcMô tả
toolbarkhôngTableToolbar above the table (search, filters, TableActions with buttons) and one below it (the count, TableActions with Pagination).
headerkhôngTableHeader with one TableRow of TableHead cells.
bodycóTableBody with one TableRow per item.
captionkhôngTableCaption, or aria-label on Table.
cellskhôngAny component in a TableCell: Avatar with a name, Badge, Checkbox in a fit cell, a DropdownMenu of row actions in a fit cell at the end.
emptykhôngTableEmpty in TableBody when there are no rows.
selectionkhôngTableSelection around the bar, the toolbars and the table, with TableSelectAll in the header's first fit cell, a TableRowSelect in each row's, and TableSelectionBar above the toolbar. Children of the bar are the app's actions on the picked rows (Export, Delete).

Cấu tạo

PhầnMô tả
rootTable: a full-width table, 14px primary text, inside a container that scrolls sideways.
headerTableHeader: the header rows. They don't highlight on hover.
rowTableRow: a row with a divider under it and the neutral hover overlay. The last body row has no divider. selected fills it with the muted accent and its strong text color.
headTableHead: a header cell, the md control height, 13px medium secondary text on one line.
cellTableCell: a data cell, px-md py-sm, that holds text or any component. align sets start, center or end; numeric sets mono, tabular figures and end; fit shrinks the column to its content.
footerTableFooter: totals on the sunken fill, medium text, a divider above.
captionTableCaption: the name of the table, under it, 13px secondary text.
sortWith onSort: a button in the header cell with the header's text and a caret, primary text on hover, a size-target-min hit area.
iconThe sort caret: Tabler ChevronUpDown, CaretUp or CaretDown, 16px, secondary.
emptyTableEmpty: one row across colSpan columns, 13px secondary text, centered, py-2xl.
toolbarTableToolbar: a row above or below the table, gap-sm, that wraps: a search Input, filters, the count of results.
actionsTableActions: the end of a toolbar, gap-sm: buttons that act on the table, or Pagination.
selectionTableSelection: a column (gap-md) around the bar, the toolbars and the table. Holds the picked rows and announces their count in a status region.
selectAllTableSelectAll: the Checkbox in the header's fit cell. On when every row on screen is picked, partly on when some are.
rowSelectTableRowSelect: the Checkbox in a row's fit cell, named "Select <label>". A checked one paints its row with the muted accent.
selectionBarTableSelectionBar: shows only while a row is picked. The count, up to max names then +N, Select all, the app's actions, Clear selection. Border, radius-container, surface fill.
bodyThe body rows. With pending they stay on screen and dim to opacity-pending (duration-medium); the container is marked aria-busy, with a polite status next to it.

Khả năng truy cập

Role: table

PhímHành động
TabMoves through the sort buttons in the headers and the controls inside cells; the table itself is not focusable.
Enter, SpaceOn a sortable header, sorts by that column.
  • A native table: screen readers read each cell with its column header. TableHead sets scope="col" by default; set scope="row" on a header cell that names its row.
  • Name the table with a TableCaption, or aria-label or aria-labelledby when the name is a heading above it.
  • A wide table scrolls sideways inside its container instead of widening the page.
  • A sortable header is a button inside the header cell; the cell carries aria-sort (ascending or descending) only for the column the table is sorted by.
  • Pick rows with a Checkbox in a fit cell, named for its row (Select Ada Lovelace); a header Checkbox picks every row. selected only paints the row: the Checkbox tells screen readers.
  • Give an icon-only actions button a name with the row in it: Actions for Ada Lovelace.
  • TableEmpty is one row across every column, so the table still has its headers when nothing matches.
  • TableSelectAll is a mixed checkbox: aria-checked is mixed while some rows are picked. A press then picks the rest; a press when all are picked clears them.
  • TableSelection keeps a status region that reads the count ("2 of 5 rows selected") whenever it changes.
  • Select all and Clear selection go away once pressed, so focus moves first: to Clear selection after Select all, to the header checkbox after Clear selection.

Cài đặt

npx shadcn add https://werewolf-ui.vercel.app/r/table.json

Lệnh này thêm luôn styles/werewolf.css, bộ token mà mọi component dùng, và các component khác mà nó cần. Import file đó một lần trong CSS gốc, ngay sau @import "tailwindcss", với đường dẫn tính từ file CSS đó (từ app/globals.css là @import "../styles/werewolf.css").