Skip to content

Tables

Native <table> semantics. Cells inherit style from descendant selectors — no per-cell class. Modifiers (striped, bordered, relaxed, sticky) compose freely. Default row height is ~32px; use relaxed for lighter layouts.

A bare .table styles <thead>, <tbody>, <tr>, <th>, and <td>. The React API mirrors the HTML via dot-notation subparts.

Name Email Role
Ada Lovelace ada@example.com Admin
Grace Hopper grace@example.com Editor
Alan Turing alan@example.com Viewer
<table class="table">
<thead>
<tr>
<th>Name</th>
<th>Email</th>
<th>Role</th>
</tr>
</thead>
<tbody>
<tr>
<td>Ada Lovelace</td>
<td>ada@example.com</td>
<td>Admin</td>
</tr>
<tr>
<td>Grace Hopper</td>
<td>grace@example.com</td>
<td>Editor</td>
</tr>
<tr>
<td>Alan Turing</td>
<td>alan@example.com</td>
<td>Viewer</td>
</tr>
</tbody>
</table>

striped zebra-stripes the body, bordered draws cell borders, relaxed increases padding, sticky pins the header (requires a scrolling ancestor — see below).

Order Status
#1001 Shipped
#1002 Processing
#1003 Shipped
#1004 Processing
<table class="table table-striped">
<thead>
<tr>
<th>Order</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<tr>
<td>#1001</td>
<td>Shipped</td>
</tr>
<tr>
<td>#1002</td>
<td>Processing</td>
</tr>
<tr>
<td>#1003</td>
<td>Shipped</td>
</tr>
<tr>
<td>#1004</td>
<td>Processing</td>
</tr>
</tbody>
</table>
SKU Name
A-001 Widget
A-002 Gadget
<table class="table table-bordered table-relaxed">
<thead>
<tr>
<th>SKU</th>
<th>Name</th>
</tr>
</thead>
<tbody>
<tr>
<td>A-001</td>
<td>Widget</td>
</tr>
<tr>
<td>A-002</td>
<td>Gadget</td>
</tr>
</tbody>
</table>

sticky pins the header while the body scrolls. The table doesn’t own the scroll region — wrap it in an overflowing container.

ID Customer
#1001 Ada
#1002 Grace
#1003 Alan
#1004 Donald
#1005 Edsger
#1006 Linus
#1007 Tony
#1008 Margaret
#1009 Barbara
#1010 Frances
<div style="overflow: auto; max-height: 240px">
<table class="table table-sticky">
<thead>
<tr>
<th>ID</th>
<th>Customer</th>
</tr>
</thead>
<tbody>
<tr>
<td>#1001</td>
<td>Ada</td>
</tr>
<tr>
<td>#1002</td>
<td>Grace</td>
</tr>
<tr>
<td>#1003</td>
<td>Alan</td>
</tr>
<tr>
<td>#1004</td>
<td>Donald</td>
</tr>
<tr>
<td>#1005</td>
<td>Edsger</td>
</tr>
<tr>
<td>#1006</td>
<td>Linus</td>
</tr>
<tr>
<td>#1007</td>
<td>Tony</td>
</tr>
<tr>
<td>#1008</td>
<td>Margaret</td>
</tr>
<tr>
<td>#1009</td>
<td>Barbara</td>
</tr>
<tr>
<td>#1010</td>
<td>Frances</td>
</tr>
</tbody>
</table>
</div>

Set data-align="right" (or "center") on <td>/<th> — or pass align to <Table.Cell> / <Table.HeaderCell>. Header and body should agree. The numeric modifier also turns on tabular-nums so currency digits don’t shimmy.

Item Qty Total
Widget 3 $129.00
Gadget 12 $1,344.50
<table class="table">
<thead>
<tr>
<th>Item</th>
<th data-align="right">Qty</th>
<th data-align="right">Total</th>
</tr>
</thead>
<tbody>
<tr>
<td>Widget</td>
<td data-align="right">3</td>
<td data-align="right">$129.00</td>
</tr>
<tr>
<td>Gadget</td>
<td data-align="right">12</td>
<td data-align="right">$1,344.50</td>
</tr>
</tbody>
</table>

A narrow leading cell for row-level status glyphs. .table-cell-gutter (or gutter prop) centers the icon and uses muted text; colour the icon explicitly when status carries meaning.

Order Customer Total
#1001 Ada Lovelace $129.00
#1002 Grace Hopper $72.50
#1003 Alan Turing $310.00
<table class="table">
<thead>
<tr>
<th class="table-cell-gutter"></th>
<th>Order</th>
<th>Customer</th>
<th data-align="right">Total</th>
</tr>
</thead>
<tbody>
<tr>
<td class="table-cell-gutter">
<i
class="ti ti-circle-check"
style="color: var(--color-success)"
aria-hidden="true"
></i>
</td>
<td>#1001</td>
<td>Ada Lovelace</td>
<td data-align="right">$129.00</td>
</tr>
<tr>
<td class="table-cell-gutter">
<i class="ti ti-clock" aria-hidden="true"></i>
</td>
<td>#1002</td>
<td>Grace Hopper</td>
<td data-align="right">$72.50</td>
</tr>
<tr>
<td class="table-cell-gutter">
<i
class="ti ti-circle-x"
style="color: var(--color-danger)"
aria-hidden="true"
></i>
</td>
<td>#1003</td>
<td>Alan Turing</td>
<td data-align="right">$310.00</td>
</tr>
</tbody>
</table>

Put a Checkbox in the first cell — row tint is driven by :has(input:checked), no state mirror needed. Wire the select-all header checkbox yourself.

Order Customer
#1001 Ada Lovelace
#1002 Grace Hopper
#1003 Alan Turing
<table class="table">
<thead>
<tr>
<th class="table-cell-gutter">
<input type="checkbox" class="checkbox" aria-label="Select all" />
</th>
<th>Order</th>
<th>Customer</th>
</tr>
</thead>
<tbody>
<tr>
<td class="table-cell-gutter">
<input type="checkbox" class="checkbox" aria-label="Select #1001" />
</td>
<td>#1001</td>
<td>Ada Lovelace</td>
</tr>
<tr>
<td class="table-cell-gutter">
<input
type="checkbox"
class="checkbox"
aria-label="Select #1002"
checked
/>
</td>
<td>#1002</td>
<td>Grace Hopper</td>
</tr>
<tr>
<td class="table-cell-gutter">
<input type="checkbox" class="checkbox" aria-label="Select #1003" />
</td>
<td>#1003</td>
<td>Alan Turing</td>
</tr>
</tbody>
</table>

For programmatic selection without a checkbox, use <Table.Row selected> — it sets data-selected and applies the same tint.

#1001 Ada
#1002 Grace
#1003 Alan
<table class="table">
<tbody>
<tr>
<td>#1001</td>
<td>Ada</td>
</tr>
<tr data-selected>
<td>#1002</td>
<td>Grace</td>
</tr>
<tr>
<td>#1003</td>
<td>Alan</td>
</tr>
</tbody>
</table>

Add .table-row-link to the <tr> (or asLink on <Table.Row>) and put the link in the first cell. CSS expands the anchor’s hit area via ::after, so the whole row is clickable and keyboard-focusable through the real <a>. Other in-row buttons and links stay clickable because they sit above the pseudo-element.

Order Customer Total
<table class="table">
<thead>
<tr>
<th>Order</th>
<th>Customer</th>
<th data-align="right">Total</th>
</tr>
</thead>
<tbody>
<tr class="table-row-link">
<td><a href="#1001">#1001</a></td>
<td>Ada Lovelace</td>
<td data-align="right">$129.00</td>
</tr>
<tr class="table-row-link">
<td><a href="#1002">#1002</a></td>
<td>Grace Hopper</td>
<td data-align="right">$72.50</td>
</tr>
<tr class="table-row-link">
<td><a href="#1003">#1003</a></td>
<td>Alan Turing</td>
<td data-align="right">$310.00</td>
</tr>
</tbody>
</table>

<Table.Foot> (or <tfoot>) is unstyled — class it yourself. Common use: a totals row mirroring the body’s column alignment.

Item Total
Widget $129.00
Gadget $72.50
Total $201.50
<table class="table">
<thead>
<tr>
<th>Item</th>
<th data-align="right">Total</th>
</tr>
</thead>
<tbody>
<tr>
<td>Widget</td>
<td data-align="right">$129.00</td>
</tr>
<tr>
<td>Gadget</td>
<td data-align="right">$72.50</td>
</tr>
</tbody>
<tfoot>
<tr>
<td><strong>Total</strong></td>
<td data-align="right"><strong>$201.50</strong></td>
</tr>
</tfoot>
</table>

<Table> doesn’t wrap itself in a scroll container. Wrap explicitly when you need horizontal scroll on narrow viewports:

<div style="overflow-x: auto">
<table class="table">
</table>
</div>