Wuko
Components

Table

Displays structured data in rows and columns. Eight thin sub-components wrap native HTML table elements so screen readers, keyboard navigation, and accessibility tools work without extra wiring. Compose freely; combine with @tanstack/react-table for sorting, filtering, and pagination.

Installation

terminal
npx shadcn@latest add @wuko/table

Usage

DeviceRegionStatus
KIOSK-4729us-westhealthy
KIOSK-3310us-eastupdating
KIOSK-3300us-westoffline
components/example.tsx
import {
  Table,
  TableBody,
  TableCell,
  TableHead,
  TableHeader,
  TableRow,
} from "@/components/ui/table";

export function Example() {
  return (
    <Table>
      <TableHeader>
        <TableRow>
          <TableHead>Device</TableHead>
          <TableHead>Region</TableHead>
          <TableHead>Status</TableHead>
        </TableRow>
      </TableHeader>
      <TableBody>
        <TableRow>
          <TableCell>KIOSK-4729</TableCell>
          <TableCell>us-west</TableCell>
          <TableCell>healthy</TableCell>
        </TableRow>
      </TableBody>
    </Table>
  );
}

Structure

Table is a compound component. Each sub-component is a thin wrapper around its native HTML counterpart, which keeps the API close to the platform and the markup readable.

text
Table
├── TableCaption
├── TableHeader
│   └── TableRow
│       ├── TableHead
│       ├── TableHead
│       └── TableHead
├── TableBody
│   ├── TableRow
│   │   ├── TableCell
│   │   ├── TableCell
│   │   └── TableCell
│   └── TableRow
│       ├── TableCell
│       ├── TableCell
│       └── TableCell
└── TableFooter

Use TableFooter for totals, summary rows, or aggregate values. It picks up muted background and medium-weight typography to separate from the body.

RegionDevices
us-west14
us-east9
Total23
tsx
<Table>
  <TableHeader>
    <TableRow>
      <TableHead>Region</TableHead>
      <TableHead>Devices</TableHead>
    </TableRow>
  </TableHeader>
  <TableBody>
    <TableRow>
      <TableCell>us-west</TableCell>
      <TableCell>14</TableCell>
    </TableRow>
    <TableRow>
      <TableCell>us-east</TableCell>
      <TableCell>9</TableCell>
    </TableRow>
  </TableBody>
  <TableFooter>
    <TableRow>
      <TableCell>Total</TableCell>
      <TableCell>23</TableCell>
    </TableRow>
  </TableFooter>
</Table>

Actions

Compose Table with DropdownMenu to add per-row actions like edit, delete, or copy. The trigger is an icon-only Button to keep the action column visually compact.

DeviceRegion
KIOSK-4729us-west
KIOSK-3310us-east
tsx
import { MoreHorizontal } from "lucide-react";

import { Button } from "@/components/ui/button";
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuLabel,
  DropdownMenuSeparator,
  DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";

<Table>
  <TableHeader>
    <TableRow>
      <TableHead>Device</TableHead>
      <TableHead>Region</TableHead>
      <TableHead className="w-12.5" />
    </TableRow>
  </TableHeader>
  <TableBody>
    <TableRow>
      <TableCell>KIOSK-4729</TableCell>
      <TableCell>us-west</TableCell>
      <TableCell>
        <DropdownMenu>
          <DropdownMenuTrigger asChild>
            <Button variant="ghost" size="icon-sm" aria-label="Open menu">
              <MoreHorizontal className="size-4" />
            </Button>
          </DropdownMenuTrigger>
          <DropdownMenuContent align="end">
            <DropdownMenuLabel>Actions</DropdownMenuLabel>
            <DropdownMenuItem>Copy device ID</DropdownMenuItem>
            <DropdownMenuSeparator />
            <DropdownMenuItem>View details</DropdownMenuItem>
            <DropdownMenuItem>Restart device</DropdownMenuItem>
          </DropdownMenuContent>
        </DropdownMenu>
      </TableCell>
    </TableRow>
  </TableBody>
</Table>

Row states

Rows hover automatically. To mark a row as selected (e.g., to pair with a checkbox column), set data-state="selected".

DeviceRegionStatus
KIOSK-4729us-westhealthy
KIOSK-3310us-eastupdating
tsx
<TableRow data-state="selected">
  <TableCell>KIOSK-4729</TableCell>
  <TableCell>us-west</TableCell>
  <TableCell>healthy</TableCell>
</TableRow>

Composing with TanStack Table

Table is intentionally headless of behavior. For sortable, filterable, and paginated tables, compose Table with @tanstack/react-table. The DataTable composition is on the roadmap; until then, this is the pattern to use directly.

tsx
// Combine with @tanstack/react-table for sorting, filtering, and pagination.
import {
  flexRender,
  getCoreRowModel,
  useReactTable,
} from "@tanstack/react-table";
import {
  Table,
  TableBody,
  TableCell,
  TableHead,
  TableHeader,
  TableRow,
} from "@/components/ui/table";

Sub-components

Every sub-component forwards className and all native attributes for its underlying element. There are no custom props beyond what HTML already provides.

PropTypeDefaultDescription
TableHTMLAttributes<HTMLTableElement>Wraps a native <table> in a scrollable <div>. The container handles horizontal overflow so wide tables don't break page layout.
TableHeaderHTMLAttributes<HTMLTableSectionElement>Renders <thead>. Adds a bottom border between header rows and body content.
TableBodyHTMLAttributes<HTMLTableSectionElement>Renders <tbody>. The last row's border is removed so the table reads cleanly into the footer or page bg.
TableFooterHTMLAttributes<HTMLTableSectionElement>Renders <tfoot>. Adds a top border, muted background, and medium-weight type for totals or summary rows.
TableRowHTMLAttributes<HTMLTableRowElement>Renders <tr>. Includes hover and selected (data-state="selected") styling.
TableHeadThHTMLAttributes<HTMLTableCellElement>Renders <th>. Uses uppercase, tracked, muted-color typography to distinguish column labels from body cells.
TableCellTdHTMLAttributes<HTMLTableCellElement>Renders <td>. Standard padding and vertical alignment match TableHead so columns align cleanly.
TableCaptionHTMLAttributes<HTMLTableCaptionElement>Renders <caption>. Use to describe the table's contents for screen readers. Visually placed below the table.

Accessibility

  • Renders native HTML table elements (<table>, <thead>, <tbody>, <tr>, etc.). Screen readers announce row and column counts, header relationships, and cell positions without additional ARIA wiring.
  • Use TableCaption to give the table a name. Screen readers announce the caption before the table contents, which orients users entering the table.
  • For columns that sort, render a button inside TableHead and toggle aria-sort on the <th> element. The base <th> already announces as a column header.
  • Avoid using tables for visual layout. If the content is not tabular, reach for flex or grid layouts instead so screen readers don't announce false structure.
  • For very wide tables, the outer scroll container preserves keyboard navigation. Users can tab through interactive cells regardless of horizontal scroll position.