Import
import {
Table,
TableBody,
TableCaption,
TableCell,
TableFooter,
TableHead,
TableHeader,
TableRow,
} from "@timelycare/helix-ui"
Structure
| Part | Tailwind |
|---|---|
| Table (container) | w-full overflow-x-auto bg-input-bg p-4 |
| Table | w-full caption-bottom text-sm |
| TableHeader | — |
| TableRow | border-b border-border transition-colors hover:bg-accent focus-visible:ring-[3px] focus-visible:ring-inset focus-visible:ring-ring/50 |
| TableHead | h-10 px-2 text-left align-middle text-sm font-semibold text-muted-foreground |
| TableCell | p-2 align-top text-sm font-normal text-foreground |
| TableFooter | border-t font-medium |
| TableCaption | pt-4 text-sm text-muted-foreground text-center |
Sizes
| Size | Cell Height | Use For |
|---|---|---|
| default | h-[52px] | Standard data tables |
| md | h-[72px] | Tables with more content |
| lg | h-[96px] | Tables with rich content (images, multi-line) |
Variants
Striped
Apply alternating row backgrounds by passing striped to TableBody. Odd-numbered rows (starting with row 1) receive bg-muted/40; even rows stay bg-input-bg. Use for dense, read-heavy tables where the eye needs help tracking across a row.
<Table>
<TableHeader>
<TableRow>
<TableHead>ID</TableHead>
<TableHead>Name</TableHead>
<TableHead>Status</TableHead>
</TableRow>
</TableHeader>
<TableBody striped>
{rows.map((row) => (
<TableRow key={row.id}>
<TableCell>{row.id}</TableCell>
<TableCell>{row.name}</TableCell>
<TableCell><Badge variant="outlineSage">{row.status}</Badge></TableCell>
</TableRow>
))}
</TableBody>
</Table>
Notes:
- Hover uses
hover:bg-accentuniformly on all rows — no special casing between stripe and non-stripe rows. - Do not combine
stripedwith row-levelbg-*class overrides — the stripe will be overridden. - Use striping only on
TableBodyrows; header and footer rows are unaffected.
Cell Variants
| Variant | Content | Use For |
|---|---|---|
| Default | Text only | Simple data |
| Bold Text | Title + description | Primary column, identifiers |
| Badge | Badge component | Status indicators |
| Avatar | Avatar + text | User columns |
| Switch | Switch toggle | Settings tables |
| Button | Action button | Row actions |
| Dropdown | DropdownMenu trigger with MoreVertical icon | Multiple actions |
| Progress | Progress bar | Completion status |
| Image | AspectRatio image | Media tables |
| Input | Input field | Editable cells |
| Toggle Group | Toggle buttons | Options selection |
States
| State | Implementation |
|---|---|
| Default | Base styling |
| Hover (all rows) | hover:bg-accent — uniform across striped and non-striped rows |
| Focus (keyboard) | focus-visible:ring-[3px] focus-visible:ring-inset focus-visible:ring-ring/50 — built into TableRow |
| Selected | bg-accent — same color as hover; selected rows have no additional hover state |
Common Patterns
Basic Table
<Table>
<TableCaption>A list of your recent invoices.</TableCaption>
<TableHeader>
<TableRow>
<TableHead>Invoice</TableHead>
<TableHead>Status</TableHead>
<TableHead className="text-right">Amount</TableHead>
</TableRow>
</TableHeader>
<TableBody>
<TableRow>
<TableCell className="font-semibold">INV001</TableCell>
<TableCell>Paid</TableCell>
<TableCell className="text-right">$250.00</TableCell>
</TableRow>
</TableBody>
</Table>
With Footer
<Table>
<TableHeader>
<TableRow>
<TableHead>Invoice</TableHead>
<TableHead className="text-right">Amount</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{/* rows */}
</TableBody>
<TableFooter>
<TableRow>
<TableCell>Total</TableCell>
<TableCell className="text-right font-semibold">$2,500.00</TableCell>
</TableRow>
</TableFooter>
</Table>
Cell with Title + Description
<TableCell>
<div className="flex flex-col">
<span className="font-semibold">John Doe</span>
<span className="text-muted-foreground">john@example.com</span>
</div>
</TableCell>
Multi-line Cell Content
Cells default to align-top. Row height expands automatically to fit content — no fixed height is applied. Wrap long text inside a max-w-* class on the cell to control column width.
<TableCell className="max-w-[260px]">
A longer description that wraps across multiple lines. The row height
expands to fit the content, and all other cells in the row align to the top.
</TableCell>
Notes:
whitespace-nowrapis not applied by default — text wraps naturally.- All cells use
align-top, so multi-line and single-line cells in the same row stay top-aligned. - Avoid setting explicit row heights (
h-[52px]etc.) on rows with variable-length content.
Cell with Avatar
<TableCell>
<div className="flex items-center gap-2">
<Avatar className="size-8">
<AvatarImage src="/avatar.jpg" />
<AvatarFallback>JD</AvatarFallback>
</Avatar>
<span>John Doe</span>
</div>
</TableCell>
Cell with Badge
<TableCell>
<Badge variant="outlineSage">Active</Badge>
</TableCell>
Right-Aligned Column (Numbers)
<TableHead className="text-right">Amount</TableHead>
<TableCell className="text-right">$250.00</TableCell>
Table with Toolbar
Data tables should include filtering and actions above.
<div className="space-y-4">
{/* Toolbar */}
<div className="flex items-center justify-between">
<Input placeholder="Filter..." className="max-w-sm" />
<div className="flex items-center gap-2">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" size="sm">
<SlidersHorizontal className="size-4 mr-2" />
View
</Button>
</DropdownMenuTrigger>
{/* Menu content */}
</DropdownMenu>
<Button size="sm">
<Plus className="size-4 mr-2" />
Add
</Button>
</div>
</div>
{/* Table */}
<div className="rounded-md border">
<Table>
<TableHeader>
<TableRow>
<TableHead className="w-[50px]"><Checkbox /></TableHead>
<TableHead>Name</TableHead>
<TableHead>Status</TableHead>
<TableHead className="w-[50px]"></TableHead>
</TableRow>
</TableHeader>
<TableBody>
{/* Rows */}
</TableBody>
</Table>
</div>
{/* Pagination */}
<div className="flex items-center justify-between">
<p className="text-sm text-muted-foreground">0 of 10 selected</p>
<div className="flex items-center gap-2">
<Button variant="outline" size="sm" disabled>Previous</Button>
<Button variant="outline" size="sm">Next</Button>
</div>
</div>
</div>
Badge in Table Cell
Status badges in table cells always use Helix outline primitive badge variants — never the shadcn default, secondary, destructive, or outline variants in table contexts.
<TableCell>
<Badge variant="outlineSage">Active</Badge>
</TableCell>
Status → Badge Variant Mapping
| Status | Badge Variant |
|---|---|
| Active / Success | outlineSage |
| Pending / In Progress | outlineNavy |
| Error / Failed | outlineBerry |
| Inactive / Disabled | outlineNeutral |
| Cancelled | outlineBerry |
Accessibility
- Standard HTML table — uses semantic markup for accessibility
- Use
<thead>,<tbody>,<th>withscope="col"for proper structure - Screen reader: header/cell associations handled automatically with proper markup
- Clickable rows: add
tabIndex={0}to<TableRow>and handleonKeyDownfor Enter/Space — the focus ring is built in viafocus-visible:ring-[3px] focus-visible:ring-inset focus-visible:ring-ring/50 - Sticky checkbox column: apply
sticky left-0 z-10 bg-input-bg(or the row's active background) to both<TableHead>and<TableCell>in the checkbox column so selection state remains visible during horizontal scroll - Add
role="grid"only if table is interactive
Gotchas
| Problem | Solution |
|---|---|
| Caption at top | Caption is always bottom by default |
| Missing hover | Ensure hover:bg-accent on TableRow |
| No keyboard focus ring | Add tabIndex={0} to the row — the ring is already in TableRow base styles and activates automatically |
| Sticky column bleeds | Set explicit bg-input-bg (or the row's active bg) on sticky <TableHead> and <TableCell> — transparent sticky cells let scrolling content show through |
| Alignment issues | Use align-middle on cells |
| Last cell border | Remove with border-b-0 if needed |
| Wide tables | Wrap in overflow-x-auto container |
See Also
- Accessibility: Data Table Accessibility Requirements - Header associations, keyboard navigation
- Related Components: Data Table (with sorting/filtering)
- Patterns: Table Behavior — Row interactivity, action placement, visual state rules
- Patterns: Layouts — Page-level layout patterns
Last updated: May 1, 2026