Table
A flat data grid for rows of structured records.
Basic
Wrap your data in .table. The first and last column cells line up with the card body gutter so a table drops into a .card flush.
| Plan | Seats | Storage | Price | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Starter | 3 | 10 GB | $0/mo | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Team | 15 | 100 GB |
TableA flat data grid for rows of structured records. BasicWrap your data in
Row variantsAdd
Striped rowsAdd
Striped columnsUse
Hoverable rowsAdd
Active rowFlag the persistent selection with
Bordered
Borderless
Small
Vertical alignmentCell content sits at the top by default. Add
CaptionA
Header altApply
With status badgesDrop a
With user avatarsStack an
With row actionsTrailing buttons go in the last cell with
Selectable rowsPut a
Pair with a header that surfaces the bulk count and actions once a row is checked. The count chip uses
2 of 4 selected
Inside a cardDrop the table straight into a
Deployments
Full dashboard tableComposes everything above. Alt-surface header, avatars in the first cell, badges for status, row actions trailing.
Team members
5 of 15 seats
Group dividerAdd
ResponsiveWrap the table in
For breakpoint-scoped scrolling, swap to CustomizationSeventeen variables retune Geometry
Head
Surface
Row states
The cell paint chain reads |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Business | 50 | 1 TB | $99/mo | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Enterprise | Unlimited | Custom | Talk to sales |
<table class="table">
<thead>
<tr>
<th scope="col">Plan</th>
<th scope="col">Seats</th>
<th scope="col">Storage</th>
<th scope="col" class="text-end">Price</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">Starter</th>
<td>3</td>
<td>10 GB</td>
<td class="text-end">$0/mo</td>
</tr>
<tr>
<th scope="row">Team</th>
<td>15</td>
<td>100 GB</td>
<td class="text-end">
Table
A flat data grid for rows of structured records.
Basic
Wrap your data in .table. The first and last column cells line up with the card body gutter so a table drops into a .card flush.
Plan
Seats
Storage
Price
Starter
3
10 GB
$0/mo
Team
15
100 GB
$29/mo
Business
50
1 TB
$99/mo
Enterprise
Unlimited
Custom
Talk to sales
<table class="table">
<thead>
<tr>
<th scope="col">Plan</th>
<th scope="col">Seats</th>
<th scope="col">Storage</th>
<th scope="col" class="text-end">Price</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">Starter</th>
<td>3</td>
<td>10 GB</td>
<td class="text-end">$0/mo</td>
</tr>
<tr>
<th scope="row">Team</th>
<td>15</td>
<td>100 GB</td>
<td class="text-end">$29/mo</td>
</tr>
<tr>
<th scope="row">Business</th>
<td>50</td>
<td>1 TB</td>
<td class="text-end">$99/mo</td>
</tr>
<tr>
<th scope="row">Enterprise</th>
<td>Unlimited</td>
<td>Custom</td>
<td class="text-end">Talk to sales</td>
</tr>
</tbody>
</table>
Row variants
Add .table__row--{intent} to a <tr> to call out a row that needs attention. Below is a job queue where one job is mid-retry and another has failed. Available intents are primary, success, info, warning, danger, plus neutral for a quiet emphasis.
Job
Schedule
Last run
Duration
nightly-backup
Daily 02:00 UTC
4 hours ago
3m 12s
events-rollup
Hourly :00
Retrying (2 of 5)
—
invoice-export
Mon 06:00 UTC
Yesterday
1m 04s
webhook-replay
Every 15 min
Failed · 12 min ago
—
metrics-aggregate
Every 5 min
2 min ago
7s
<table class="table table--align-middle">
<thead>
<tr>
<th scope="col">Job</th>
<th scope="col">Schedule</th>
<th scope="col">Last run</th>
<th scope="col" class="text-end">Duration</th>
</tr>
</thead>
<tbody>
<tr>
<td>nightly-backup</td>
<td>Daily 02:00 UTC</td>
<td>4 hours ago</td>
<td class="text-end">3m 12s</td>
</tr>
<tr class="table__row--warning">
<td>events-rollup</td>
<td>Hourly :00</td>
<td>Retrying (2 of 5)</td>
<td class="text-end">—</td>
</tr>
<tr>
<td>invoice-export</td>
<td>Mon 06:00 UTC</td>
<td>Yesterday</td>
<td class="text-end">1m 04s</td>
</tr>
<tr class="table__row--danger">
<td>webhook-replay</td>
<td>Every 15 min</td>
<td>Failed · 12 min ago</td>
<td class="text-end">—</td>
</tr>
<tr>
<td>metrics-aggregate</td>
<td>Every 5 min</td>
<td>2 min ago</td>
<td class="text-end">7s</td>
</tr>
</tbody>
</table>
Striped rows
Add .table--striped to zebra-stripe every other row in the body.
Region
Latency p50
Latency p99
Requests / min
us-east-1 42 ms 180 ms 12,840
us-west-2 58 ms 220 ms 9,210
eu-west-1 61 ms 240 ms 7,455
ap-southeast-1 74 ms 310 ms 3,902
sa-east-1 110 ms 420 ms 1,288
<table class="table table--striped">
<thead>
<tr>
<th scope="col">Region</th>
<th scope="col">Latency p50</th>
<th scope="col">Latency p99</th>
<th scope="col" class="text-end">Requests / min</th>
</tr>
</thead>
<tbody>
<tr><td>us-east-1</td><td>42 ms</td><td>180 ms</td><td class="text-end">12,840</td></tr>
<tr><td>us-west-2</td><td>58 ms</td><td>220 ms</td><td class="text-end">9,210</td></tr>
<tr><td>eu-west-1</td><td>61 ms</td><td>240 ms</td><td class="text-end">7,455</td></tr>
<tr><td>ap-southeast-1</td><td>74 ms</td><td>310 ms</td><td class="text-end">3,902</td></tr>
<tr><td>sa-east-1</td><td>110 ms</td><td>420 ms</td><td class="text-end">1,288</td></tr>
</tbody>
</table>
Striped columns
Use .table--striped-cols when the table reads column-first.
Quarter
Q1
Q2
Q3
Q4
New accounts
418 502 611 730
Churned
62 71 54 48
Net new ARR
$182k $214k $273k $331k
<table class="table table--striped-cols">
<thead>
<tr>
<th scope="col">Quarter</th>
<th scope="col">Q1</th>
<th scope="col">Q2</th>
<th scope="col">Q3</th>
<th scope="col">Q4</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">New accounts</th>
<td>418</td><td>502</td><td>611</td><td>730</td>
</tr>
<tr>
<th scope="row">Churned</th>
<td>62</td><td>71</td><td>54</td><td>48</td>
</tr>
<tr>
<th scope="row">Net new ARR</th>
<td>$182k</td><td>$214k</td><td>$273k</td><td>$331k</td>
</tr>
</tbody>
</table>
Hoverable rows
Add .table--hover to highlight the row under the cursor. Composes with .table--striped.
Endpoint
Method
Calls (24h)
Error rate
/v1/chargesPOST 48,210 0.12%
/v1/customersGET 31,089 0.04%
/v1/invoicesPOST 9,402 0.31%
/v1/webhooksPOST 2,118 1.84%
<table class="table table--hover">
<thead>
<tr>
<th scope="col">Endpoint</th>
<th scope="col">Method</th>
<th scope="col">Calls (24h)</th>
<th scope="col" class="text-end">Error rate</th>
</tr>
</thead>
<tbody>
<tr><td><code>/v1/charges</code></td><td>POST</td><td>48,210</td><td class="text-end">0.12%</td></tr>
<tr><td><code>/v1/customers</code></td><td>GET</td><td>31,089</td><td class="text-end">0.04%</td></tr>
<tr><td><code>/v1/invoices</code></td><td>POST</td><td>9,402</td><td class="text-end">0.31%</td></tr>
<tr><td><code>/v1/webhooks</code></td><td>POST</td><td>2,118</td><td class="text-end">1.84%</td></tr>
</tbody>
</table>
Active row
Flag the persistent selection with data-state="active" on the <tr>. Runs through the highlight tier, same as sidebar and dropdown selection.
Branch
Last commit
Author
Behind / Ahead
main Bump axios to 1.7.4 Mateo Reyes 0 / 0
feat/billing-v2 Add proration preview Maya Singh 0 / 14
fix/race-on-replay Drain queue before close Theo Wright 3 / 2
chore/upgrade-node-20 Pin engines field Sara Lin 6 / 1
<table class="table">
<thead>
<tr>
<th scope="col">Branch</th>
<th scope="col">Last commit</th>
<th scope="col">Author</th>
<th scope="col" class="text-end">Behind / Ahead</th>
</tr>
</thead>
<tbody>
<tr><td>main</td><td>Bump axios to 1.7.4</td><td>Mateo Reyes</td><td class="text-end">0 / 0</td></tr>
<tr data-state="active"><td>feat/billing-v2</td><td>Add proration preview</td><td>Maya Singh</td><td class="text-end">0 / 14</td></tr>
<tr><td>fix/race-on-replay</td><td>Drain queue before close</td><td>Theo Wright</td><td class="text-end">3 / 2</td></tr>
<tr><td>chore/upgrade-node-20</td><td>Pin engines field</td><td>Sara Lin</td><td class="text-end">6 / 1</td></tr>
</tbody>
</table>
Bordered
.table--bordered draws a border on every side of every cell.
Cluster
Nodes
CPU
Memory
prod-edge 24 62% 71%
prod-core 48 54% 68%
staging 6 18% 22%
<table class="table table--bordered">
<thead>
<tr>
<th scope="col">Cluster</th>
<th scope="col">Nodes</th>
<th scope="col">CPU</th>
<th scope="col">Memory</th>
</tr>
</thead>
<tbody>
<tr><td>prod-edge</td><td>24</td><td>62%</td><td>71%</td></tr>
<tr><td>prod-core</td><td>48</td><td>54%</td><td>68%</td></tr>
<tr><td>staging</td><td>6</td><td>18%</td><td>22%</td></tr>
</tbody>
</table>
Borderless
.table--borderless strips every row and cell border for a soft list look.
Project
Owner
Open issues
billing-service Maya Singh 12
auth-gateway Mateo Reyes 4
events-pipeline Sara Lin 7
web-dashboard Theo Wright 23
<table class="table table--borderless">
<thead>
<tr>
<th scope="col">Project</th>
<th scope="col">Owner</th>
<th scope="col" class="text-end">Open issues</th>
</tr>
</thead>
<tbody>
<tr><td>billing-service</td><td>Maya Singh</td><td class="text-end">12</td></tr>
<tr><td>auth-gateway</td><td>Mateo Reyes</td><td class="text-end">4</td></tr>
<tr><td>events-pipeline</td><td>Sara Lin</td><td class="text-end">7</td></tr>
<tr><td>web-dashboard</td><td>Theo Wright</td><td class="text-end">23</td></tr>
</tbody>
</table>
Small
.table--sm shrinks inner cell padding for denser rows. Edge padding stays so the flush-in-card alignment holds.
Key
Value
Last edited
SMTP_HOSTsmtp.postmark.io Maya · 3 days ago
SMTP_PORT587 Maya · 3 days ago
S3_BUCKETstisla-assets-prod Theo · last week
FEATURE_BILLING_V2true Mateo · 1 hour ago
RATE_LIMIT_RPM600 Sara · yesterday
<table class="table table--sm">
<thead>
<tr>
<th scope="col">Key</th>
<th scope="col">Value</th>
<th scope="col">Last edited</th>
</tr>
</thead>
<tbody>
<tr><td><code>SMTP_HOST</code></td><td>smtp.postmark.io</td><td>Maya · 3 days ago</td></tr>
<tr><td><code>SMTP_PORT</code></td><td>587</td><td>Maya · 3 days ago</td></tr>
<tr><td><code>S3_BUCKET</code></td><td>stisla-assets-prod</td><td>Theo · last week</td></tr>
<tr><td><code>FEATURE_BILLING_V2</code></td><td>true</td><td>Mateo · 1 hour ago</td></tr>
<tr><td><code>RATE_LIMIT_RPM</code></td><td>600</td><td>Sara · yesterday</td></tr>
</tbody>
</table>
Vertical alignment
Cell content sits at the top by default. Add .table--align-middle when rows mix multi-line text with shorter values.
Document
Summary
Pages
Onboarding handbook
Day-1 setup, IT accounts, payroll forms, and the company-wide reading list new hires work through in their first week.
42
Security policy
Device requirements, password rules, incident reporting, and the quarterly review schedule.
18
Engineering RFCs
Active proposals up for review this cycle. Each row links to the long-form doc.
7
<table class="table table--align-middle">
<thead>
<tr>
<th scope="col">Document</th>
<th scope="col">Summary</th>
<th scope="col" class="text-end">Pages</th>
</tr>
</thead>
<tbody>
<tr>
<td>Onboarding handbook</td>
<td>Day-1 setup, IT accounts, payroll forms, and the company-wide reading list new hires work through in their first week.</td>
<td class="text-end">42</td>
</tr>
<tr>
<td>Security policy</td>
<td>Device requirements, password rules, incident reporting, and the quarterly review schedule.</td>
<td class="text-end">18</td>
</tr>
<tr>
<td>Engineering RFCs</td>
<td>Active proposals up for review this cycle. Each row links to the long-form doc.</td>
<td class="text-end">7</td>
</tr>
</tbody>
</table>
Caption
A <caption> sits below the table and reads like a footnote. Promote it above the table with .caption-top.
Last refreshed at 09:42 UTC
Job
Schedule
Last run
weekly-invoice-rollup Mon 02:00 UTC 2 days ago · succeeded
nightly-vacuum Daily 03:30 UTC 6 hours ago · succeeded
hourly-replay Hourly :15 27 min ago · failed
<table class="table">
<caption>Last refreshed at 09:42 UTC</caption>
<thead>
<tr>
<th scope="col">Job</th>
<th scope="col">Schedule</th>
<th scope="col">Last run</th>
</tr>
</thead>
<tbody>
<tr><td>weekly-invoice-rollup</td><td>Mon 02:00 UTC</td><td>2 days ago · succeeded</td></tr>
<tr><td>nightly-vacuum</td><td>Daily 03:30 UTC</td><td>6 hours ago · succeeded</td></tr>
<tr><td>hourly-replay</td><td>Hourly :15</td><td>27 min ago · failed</td></tr>
</tbody>
</table>
Header alt
Apply .table__head--alt on the <thead> to opt the header row onto the alt surface. Same two-tone language as .card__header--alt.
Customer
Plan
MRR
Renews
Acme Corp Business $1,490 Aug 12
Riverway Ltd Team $580 Sep 04
Northwind Enterprise $8,200 Oct 22
Globex Team $580 Nov 09
<table class="table">
<thead class="table__head--alt">
<tr>
<th scope="col">Customer</th>
<th scope="col">Plan</th>
<th scope="col">MRR</th>
<th scope="col" class="text-end">Renews</th>
</tr>
</thead>
<tbody>
<tr><td>Acme Corp</td><td>Business</td><td>$1,490</td><td class="text-end">Aug 12</td></tr>
<tr><td>Riverway Ltd</td><td>Team</td><td>$580</td><td class="text-end">Sep 04</td></tr>
<tr><td>Northwind</td><td>Enterprise</td><td>$8,200</td><td class="text-end">Oct 22</td></tr>
<tr><td>Globex</td><td>Team</td><td>$580</td><td class="text-end">Nov 09</td></tr>
</tbody>
</table>
With status badges
Drop a .badge into a cell to flag state. Soft .badge--soft variants read cleaner inside a dense row than solid fills.
Invoice
Client
Amount
Due
Status
INV-1042
Acme Corp
$4,800.00
Jun 15
Sent
INV-1041
Riverway Ltd
$1,250.00
Jun 10
Paid
INV-1040
Northwind
$9,310.00
May 28
Overdue
INV-1039
Globex
$2,140.00
Jun 22
Draft
INV-1038
Initech
$680.00
Jun 30
Scheduled
<table class="table table--hover table--align-middle">
<thead class="table__head--alt">
<tr>
<th scope="col">Invoice</th>
<th scope="col">Client</th>
<th scope="col">Amount</th>
<th scope="col">Due</th>
<th scope="col">Status</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>INV-1042</code></td>
<td>Acme Corp</td>
<td>$4,800.00</td>
<td>Jun 15</td>
<td><span class="badge badge--soft badge--info"><i data-lucide="send"></i> Sent</span></td>
</tr>
<tr>
<td><code>INV-1041</code></td>
<td>Riverway Ltd</td>
<td>$1,250.00</td>
<td>Jun 10</td>
<td><span class="badge badge--soft badge--success"><i data-lucide="check"></i> Paid</span></td>
</tr>
<tr>
<td><code>INV-1040</code></td>
<td>Northwind</td>
<td>$9,310.00</td>
<td>May 28</td>
<td><span class="badge badge--soft badge--danger"><i data-lucide="alert-triangle"></i> Overdue</span></td>
</tr>
<tr>
<td><code>INV-1039</code></td>
<td>Globex</td>
<td>$2,140.00</td>
<td>Jun 22</td>
<td><span class="badge badge--soft badge--warning"><i data-lucide="clock"></i> Draft</span></td>
</tr>
<tr>
<td><code>INV-1038</code></td>
<td>Initech</td>
<td>$680.00</td>
<td>Jun 30</td>
<td><span class="badge badge--soft">Scheduled</span></td>
</tr>
</tbody>
</table>
With user avatars
Stack an <img> and a <div> in a flex cell to pair an avatar with a name and a secondary line. The image keeps its natural shape, so set border-radius: 50% and a width attribute.
Member
Role
Joined
Status
Maya Singh
maya@acme.co
Admin
Jan 2024
Active
Mateo Reyes
mateo@acme.co
Editor
Mar 2024
Active
Sara Lin
sara@acme.co
Viewer
May 2024
Invite pending
Theo Wright
theo@acme.co
Editor
Jun 2023
Suspended
<table class="table table--align-middle">
<thead class="table__head--alt">
<tr>
<th scope="col">Member</th>
<th scope="col">Role</th>
<th scope="col">Joined</th>
<th scope="col">Status</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<div class="d-flex align-items-center gap-2">
<img src="https://images.unsplash.com/photo-1494790108377-be9c29b29330?auto=format&fit=facearea&facepad=2.5&w=120&h=120&q=80" alt="" width="32" height="32" style="border-radius: 50%;" />
<div>
<div>Maya Singh</div>
<div class="text-muted-foreground">maya@acme.co</div>
</div>
</div>
</td>
<td>Admin</td>
<td>Jan 2024</td>
<td><span class="badge badge--soft badge--success">Active</span></td>
</tr>
<tr>
<td>
<div class="d-flex align-items-center gap-2">
<img src="https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?auto=format&fit=facearea&facepad=2.5&w=120&h=120&q=80" alt="" width="32" height="32" style="border-radius: 50%;" />
<div>
<div>Mateo Reyes</div>
<div class="text-muted-foreground">mateo@acme.co</div>
</div>
</div>
</td>
<td>Editor</td>
<td>Mar 2024</td>
<td><span class="badge badge--soft badge--success">Active</span></td>
</tr>
<tr>
<td>
<div class="d-flex align-items-center gap-2">
<img src="https://images.unsplash.com/photo-1438761681033-6461ffad8d80?auto=format&fit=facearea&facepad=2.5&w=120&h=120&q=80" alt="" width="32" height="32" style="border-radius: 50%;" />
<div>
<div>Sara Lin</div>
<div class="text-muted-foreground">sara@acme.co</div>
</div>
</div>
</td>
<td>Viewer</td>
<td>May 2024</td>
<td><span class="badge badge--soft badge--warning">Invite pending</span></td>
</tr>
<tr>
<td>
<div class="d-flex align-items-center gap-2">
<img src="https://images.unsplash.com/photo-1500648767791-00dcc994a43e?auto=format&fit=facearea&facepad=2.5&w=120&h=120&q=80" alt="" width="32" height="32" style="border-radius: 50%;" />
<div>
<div>Theo Wright</div>
<div class="text-muted-foreground">theo@acme.co</div>
</div>
</div>
</td>
<td>Editor</td>
<td>Jun 2023</td>
<td><span class="badge badge--soft badge--danger">Suspended</span></td>
</tr>
</tbody>
</table>
With row actions
Trailing buttons go in the last cell with .text-end. Ghost icon buttons sit quiet until the row is hovered.
API key
Scope
Created
Last used
Actions
sk_live_•••••8a1c
Live
Apr 12, 2024
2 mins ago
sk_test_•••••3f02
Test
Jan 03, 2024
1 hour ago
sk_live_•••••c4e9
Live
Nov 28, 2023
3 days ago
<table class="table table--hover table--align-middle">
<thead class="table__head--alt">
<tr>
<th scope="col">API key</th>
<th scope="col">Scope</th>
<th scope="col">Created</th>
<th scope="col">Last used</th>
<th scope="col" class="text-end">Actions</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>sk_live_•••••8a1c</code></td>
<td><span class="badge badge--soft badge--primary">Live</span></td>
<td>Apr 12, 2024</td>
<td>2 mins ago</td>
<td class="text-end">
<div class="d-inline-flex gap-1">
<button type="button" class="btn btn--sm btn--ghost btn--neutral btn--icon-only" aria-label="Copy">
<i data-lucide="copy"></i>
</button>
<button type="button" class="btn btn--sm btn--ghost btn--neutral btn--icon-only" aria-label="Revoke">
<i data-lucide="trash-2"></i>
</button>
</div>
</td>
</tr>
<tr>
<td><code>sk_test_•••••3f02</code></td>
<td><span class="badge badge--soft">Test</span></td>
<td>Jan 03, 2024</td>
<td>1 hour ago</td>
<td class="text-end">
<div class="d-inline-flex gap-1">
<button type="button" class="btn btn--sm btn--ghost btn--neutral btn--icon-only" aria-label="Copy">
<i data-lucide="copy"></i>
</button>
<button type="button" class="btn btn--sm btn--ghost btn--neutral btn--icon-only" aria-label="Revoke">
<i data-lucide="trash-2"></i>
</button>
</div>
</td>
</tr>
<tr>
<td><code>sk_live_•••••c4e9</code></td>
<td><span class="badge badge--soft badge--primary">Live</span></td>
<td>Nov 28, 2023</td>
<td>3 days ago</td>
<td class="text-end">
<div class="d-inline-flex gap-1">
<button type="button" class="btn btn--sm btn--ghost btn--neutral btn--icon-only" aria-label="Copy">
<i data-lucide="copy"></i>
</button>
<button type="button" class="btn btn--sm btn--ghost btn--neutral btn--icon-only" aria-label="Revoke">
<i data-lucide="trash-2"></i>
</button>
</div>
</td>
</tr>
</tbody>
</table>
Selectable rows
Put a .checkbox in the first column to let people pick rows. The header checkbox is the bulk toggle; flip data-state="active" on the row to mark it selected. The data-demo-select-rows hook on the table wires the live behavior.
Email
Source
Subscribed
Status
maya@acme.co
Landing page
Jun 02
Confirmed
mateo@acme.co
Referral · Sara
Jun 01
Confirmed
sara@acme.co
Webinar · Q2 launch
May 28
Pending
theo@acme.co
Import · Mailchimp
May 22
Unsubscribed
alex@acme.co
Landing page
May 19
Confirmed
<table class="table table--hover table--align-middle" data-demo-select-rows>
<thead class="table__head--alt">
<tr>
<th scope="col" style="width: 1rem;">
<input class="checkbox" type="checkbox" aria-label="Select all subscribers" />
</th>
<th scope="col">Email</th>
<th scope="col">Source</th>
<th scope="col">Subscribed</th>
<th scope="col">Status</th>
</tr>
</thead>
<tbody>
<tr>
<td><input class="checkbox" type="checkbox" aria-label="Select maya@acme.co" /></td>
<td>maya@acme.co</td>
<td>Landing page</td>
<td>Jun 02</td>
<td><span class="badge badge--soft badge--success">Confirmed</span></td>
</tr>
<tr data-state="active">
<td><input class="checkbox" type="checkbox" aria-label="Select mateo@acme.co" checked /></td>
<td>mateo@acme.co</td>
<td>Referral · Sara</td>
<td>Jun 01</td>
<td><span class="badge badge--soft badge--success">Confirmed</span></td>
</tr>
<tr data-state="active">
<td><input class="checkbox" type="checkbox" aria-label="Select sara@acme.co" checked /></td>
<td>sara@acme.co</td>
<td>Webinar · Q2 launch</td>
<td>May 28</td>
<td><span class="badge badge--soft badge--warning">Pending</span></td>
</tr>
<tr>
<td><input class="checkbox" type="checkbox" aria-label="Select theo@acme.co" /></td>
<td>theo@acme.co</td>
<td>Import · Mailchimp</td>
<td>May 22</td>
<td><span class="badge badge--soft">Unsubscribed</span></td>
</tr>
<tr>
<td><input class="checkbox" type="checkbox" aria-label="Select alex@acme.co" /></td>
<td>alex@acme.co</td>
<td>Landing page</td>
<td>May 19</td>
<td><span class="badge badge--soft badge--success">Confirmed</span></td>
</tr>
</tbody>
</table>
Pair with a header that surfaces the bulk count and actions once a row is checked. The count chip uses data-demo-select-count so the script can update it as rows toggle.
2 of 4 selected
From
Subject
Received
Maya Singh
Re: Q3 roadmap draft
9:42 AM
Mateo Reyes
Auth migration status
8:18 AM
Sara Lin
Pipeline retry logic
Yesterday
Theo Wright
Vendor renewal · Jun 30
Yesterday
<div class="card">
<div class="card__header card__header--alt">
<span><strong data-demo-select-count>2</strong> of 4 selected</span>
<div class="d-flex gap-2 ms-auto">
<button type="button" class="btn btn--sm btn--outline btn--neutral">
<i data-lucide="tag"></i>
Add tag
</button>
<button type="button" class="btn btn--sm btn--outline btn--neutral">
<i data-lucide="archive"></i>
Archive
</button>
<button type="button" class="btn btn--sm btn--danger">
<i data-lucide="trash-2"></i>
Delete
</button>
</div>
</div>
<table class="table table--hover table--align-middle mb-0" data-demo-select-rows>
<thead>
<tr>
<th scope="col" style="width: 1rem;">
<input class="checkbox" type="checkbox" aria-label="Select all messages" />
</th>
<th scope="col">From</th>
<th scope="col">Subject</th>
<th scope="col" class="text-end">Received</th>
</tr>
</thead>
<tbody>
<tr data-state="active">
<td><input class="checkbox" type="checkbox" aria-label="Select message from Maya Singh" checked /></td>
<td>Maya Singh</td>
<td>Re: Q3 roadmap draft</td>
<td class="text-end">9:42 AM</td>
</tr>
<tr data-state="active">
<td><input class="checkbox" type="checkbox" aria-label="Select message from Mateo Reyes" checked /></td>
<td>Mateo Reyes</td>
<td>Auth migration status</td>
<td class="text-end">8:18 AM</td>
</tr>
<tr>
<td><input class="checkbox" type="checkbox" aria-label="Select message from Sara Lin" /></td>
<td>Sara Lin</td>
<td>Pipeline retry logic</td>
<td class="text-end">Yesterday</td>
</tr>
<tr>
<td><input class="checkbox" type="checkbox" aria-label="Select message from Theo Wright" /></td>
<td>Theo Wright</td>
<td>Vendor renewal · Jun 30</td>
<td class="text-end">Yesterday</td>
</tr>
</tbody>
</table>
</div>
Inside a card
Drop the table straight into a .card with margin-bottom: 0 so it sits flush. The edge cells line up with the card header's left padding.
Deployments
Service
Environment
Version
Deployed
Status
api
production
v2.14.0
3 min ago
Healthy
web
production
v3.41.2
12 min ago
Degraded
worker
staging
v0.8.1
1 hour ago
Healthy
api
staging
v2.15.0-rc1
just now
Deploying
<div class="card">
<div class="card__header card__header--alt">
Deployments
<button type="button" class="btn btn--sm btn--primary ms-auto">Deploy</button>
</div>
<table class="table table--hover table--align-middle mb-0">
<thead>
<tr>
<th scope="col">Service</th>
<th scope="col">Environment</th>
<th scope="col">Version</th>
<th scope="col">Deployed</th>
<th scope="col">Status</th>
</tr>
</thead>
<tbody>
<tr>
<td>api</td>
<td><span class="badge badge--soft">production</span></td>
<td><code>v2.14.0</code></td>
<td>3 min ago</td>
<td><span class="badge badge--soft badge--success"><i data-lucide="check-circle-2"></i> Healthy</span></td>
</tr>
<tr>
<td>web</td>
<td><span class="badge badge--soft">production</span></td>
<td><code>v3.41.2</code></td>
<td>12 min ago</td>
<td><span class="badge badge--soft badge--warning"><i data-lucide="triangle-alert"></i> Degraded</span></td>
</tr>
<tr>
<td>worker</td>
<td><span class="badge badge--soft">staging</span></td>
<td><code>v0.8.1</code></td>
<td>1 hour ago</td>
<td><span class="badge badge--soft badge--success"><i data-lucide="check-circle-2"></i> Healthy</span></td>
</tr>
<tr>
<td>api</td>
<td><span class="badge badge--soft">staging</span></td>
<td><code>v2.15.0-rc1</code></td>
<td>just now</td>
<td>
<span class="badge badge--soft badge--info">
<span class="spinner spinner--sm" role="status" aria-hidden="true"></span>
Deploying
</span>
</td>
</tr>
</tbody>
</table>
</div>
Full dashboard table
Composes everything above. Alt-surface header, avatars in the first cell, badges for status, row actions trailing.
Team members
5 of 15 seats
Member
Role
Last active
Status
Actions
Alex Park
alex@acme.co
Owner
just now
Active
Maya Singh
maya@acme.co
Admin
12 min ago
Active
Mateo Reyes
mateo@acme.co
Editor
2 hours ago
Active
Sara Lin
sara@acme.co
Viewer
never
Invite pending
Theo Wright
theo@acme.co
Editor
1 week ago
Suspended
<div class="card">
<div class="card__header card__header--alt">
Team members
<span class="badge badge--soft ms-2">5 of 15 seats</span>
<button type="button" class="btn btn--sm btn--primary ms-auto">
<i data-lucide="user-plus"></i>
Invite
</button>
</div>
<table class="table table--hover table--align-middle mb-0">
<thead>
<tr>
<th scope="col">Member</th>
<th scope="col">Role</th>
<th scope="col">Last active</th>
<th scope="col">Status</th>
<th scope="col" class="text-end">Actions</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<div class="d-flex align-items-center gap-2">
<img src="https://images.unsplash.com/photo-1535713875002-d1d0cf377fde?auto=format&fit=facearea&facepad=2.5&w=120&h=120&q=80" alt="" width="32" height="32" style="border-radius: 50%;" />
<div>
<div>Alex Park</div>
<div class="text-muted-foreground">alex@acme.co</div>
</div>
</div>
</td>
<td><span class="badge badge--soft badge--primary">Owner</span></td>
<td>just now</td>
<td><span class="badge badge--soft badge--success">Active</span></td>
<td class="text-end">
<button type="button" class="btn btn--sm btn--ghost btn--neutral btn--icon-only" aria-label="More">
<i data-lucide="more-horizontal"></i>
</button>
</td>
</tr>
<tr>
<td>
<div class="d-flex align-items-center gap-2">
<img src="https://images.unsplash.com/photo-1494790108377-be9c29b29330?auto=format&fit=facearea&facepad=2.5&w=120&h=120&q=80" alt="" width="32" height="32" style="border-radius: 50%;" />
<div>
<div>Maya Singh</div>
<div class="text-muted-foreground">maya@acme.co</div>
</div>
</div>
</td>
<td><span class="badge badge--soft badge--info">Admin</span></td>
<td>12 min ago</td>
<td><span class="badge badge--soft badge--success">Active</span></td>
<td class="text-end">
<button type="button" class="btn btn--sm btn--ghost btn--neutral btn--icon-only" aria-label="More">
<i data-lucide="more-horizontal"></i>
</button>
</td>
</tr>
<tr>
<td>
<div class="d-flex align-items-center gap-2">
<img src="https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?auto=format&fit=facearea&facepad=2.5&w=120&h=120&q=80" alt="" width="32" height="32" style="border-radius: 50%;" />
<div>
<div>Mateo Reyes</div>
<div class="text-muted-foreground">mateo@acme.co</div>
</div>
</div>
</td>
<td><span class="badge badge--soft">Editor</span></td>
<td>2 hours ago</td>
<td><span class="badge badge--soft badge--success">Active</span></td>
<td class="text-end">
<button type="button" class="btn btn--sm btn--ghost btn--neutral btn--icon-only" aria-label="More">
<i data-lucide="more-horizontal"></i>
</button>
</td>
</tr>
<tr>
<td>
<div class="d-flex align-items-center gap-2">
<img src="https://images.unsplash.com/photo-1438761681033-6461ffad8d80?auto=format&fit=facearea&facepad=2.5&w=120&h=120&q=80" alt="" width="32" height="32" style="border-radius: 50%;" />
<div>
<div>Sara Lin</div>
<div class="text-muted-foreground">sara@acme.co</div>
</div>
</div>
</td>
<td><span class="badge badge--soft">Viewer</span></td>
<td>never</td>
<td><span class="badge badge--soft badge--warning">Invite pending</span></td>
<td class="text-end">
<button type="button" class="btn btn--sm btn--ghost btn--neutral btn--icon-only" aria-label="More">
<i data-lucide="more-horizontal"></i>
</button>
</td>
</tr>
<tr>
<td>
<div class="d-flex align-items-center gap-2">
<img src="https://images.unsplash.com/photo-1500648767791-00dcc994a43e?auto=format&fit=facearea&facepad=2.5&w=120&h=120&q=80" alt="" width="32" height="32" style="border-radius: 50%;" />
<div>
<div>Theo Wright</div>
<div class="text-muted-foreground">theo@acme.co</div>
</div>
</div>
</td>
<td><span class="badge badge--soft">Editor</span></td>
<td>1 week ago</td>
<td><span class="badge badge--soft badge--danger">Suspended</span></td>
<td class="text-end">
<button type="button" class="btn btn--sm btn--ghost btn--neutral btn--icon-only" aria-label="More">
<i data-lucide="more-horizontal"></i>
</button>
</td>
</tr>
</tbody>
</table>
</div>
Group divider
Add .table__body--divider to a <tbody> for a heavier rule above it. Helpful when the head reads as a column label rather than a section break.
Item
Qty
Line total
Pro seat license 15 $435.00
Storage add-on 2 $40.00
Priority support 1 $199.00
<table class="table">
<thead>
<tr>
<th scope="col">Item</th>
<th scope="col">Qty</th>
<th scope="col" class="text-end">Line total</th>
</tr>
</thead>
<tbody class="table__body--divider">
<tr><td>Pro seat license</td><td>15</td><td class="text-end">$435.00</td></tr>
<tr><td>Storage add-on</td><td>2</td><td class="text-end">$40.00</td></tr>
<tr><td>Priority support</td><td>1</td><td class="text-end">$199.00</td></tr>
</tbody>
</table>
Responsive
Wrap the table in .table-responsive when it might overflow narrow viewports. The wrapper scrolls horizontally; the table itself stays unchanged.
Customer
Plan
Seats
MRR
Started
Trial ends
Renews
Owner
Status
Acme Corp Business 48 $1,490
Feb 02, 2024 — Aug 12 Maya Singh
Active
Riverway Ltd Team 14 $580
May 14, 2024 — Sep 04 Mateo Reyes
Active
Northwind Enterprise 112 $8,200
Aug 30, 2022 — Oct 22 Alex Park
Active
Globex Team 9 $580
Jun 01, 2024 Jun 15 — Sara Lin
Trialing
Initech Starter 3 $0
Jun 03, 2024 — — Theo Wright
Free
<div class="table-responsive">
<table class="table">
<thead class="table__head--alt">
<tr>
<th scope="col">Customer</th>
<th scope="col">Plan</th>
<th scope="col">Seats</th>
<th scope="col">MRR</th>
<th scope="col">Started</th>
<th scope="col">Trial ends</th>
<th scope="col">Renews</th>
<th scope="col">Owner</th>
<th scope="col">Status</th>
</tr>
</thead>
<tbody>
<tr>
<td>Acme Corp</td><td>Business</td><td>48</td><td>$1,490</td>
<td>Feb 02, 2024</td><td>—</td><td>Aug 12</td><td>Maya Singh</td>
<td><span class="badge badge--soft badge--success">Active</span></td>
</tr>
<tr>
<td>Riverway Ltd</td><td>Team</td><td>14</td><td>$580</td>
<td>May 14, 2024</td><td>—</td><td>Sep 04</td><td>Mateo Reyes</td>
<td><span class="badge badge--soft badge--success">Active</span></td>
</tr>
<tr>
<td>Northwind</td><td>Enterprise</td><td>112</td><td>$8,200</td>
<td>Aug 30, 2022</td><td>—</td><td>Oct 22</td><td>Alex Park</td>
<td><span class="badge badge--soft badge--success">Active</span></td>
</tr>
<tr>
<td>Globex</td><td>Team</td><td>9</td><td>$580</td>
<td>Jun 01, 2024</td><td>Jun 15</td><td>—</td><td>Sara Lin</td>
<td><span class="badge badge--soft badge--warning">Trialing</span></td>
</tr>
<tr>
<td>Initech</td><td>Starter</td><td>3</td><td>$0</td>
<td>Jun 03, 2024</td><td>—</td><td>—</td><td>Theo Wright</td>
<td><span class="badge badge--soft">Free</span></td>
</tr>
</tbody>
</table>
</div>
For breakpoint-scoped scrolling, swap to .table-responsive-{sm|md|lg|xl|xxl}. The wrapper switches between scroll and natural layout at the named breakpoint.
Customization
Seventeen variables retune .table without touching component CSS. Override on .table itself, on a parent scope, or on :root, and the cascade scopes the change.
Geometry
Variable Default Use
--table-cell-padding-y
calc(0.75rem * var(--st-density))
Vertical cell padding
--table-cell-padding-x
calc(0.75rem * var(--st-density))
Horizontal cell padding
--table-cell-padding-sm
calc(0.25rem * var(--st-density))
Cell padding under .table--sm
--table-edge-padding
calc(1.25rem * var(--st-density))
First and last column inset, lines up with the card body gutter
--table-group-divider-width
2px
Rule above .table__body--divider
Head
Variable Default Use
--table-head-font-size
0.75rem
Header label size
--table-head-font-weight
500
Header label weight (down from browser-default 700)
--table-head-color
var(--st-muted-foreground)
Header label color
--table-head-bg-alt
var(--st-surface-2)
Fill applied by .table__head--alt
Surface
Variable Default Use
--table-color
var(--st-foreground)
Body text color
--table-bg
transparent
Cell background (rest layer)
--table-border-color
var(--st-border)
Row and cell border color
Row states
Variable Default Use
--table-striped-bg
color-mix(in oklch, var(--st-foreground) 4%, transparent)
Odd row / even column tint
--table-striped-color
var(--st-foreground)
Striped row text color
--table-hover-bg
var(--st-accent)
Row hover background under .table--hover
--table-hover-color
var(--st-accent-foreground)
Row hover text color
--table-active-bg
var(--st-highlight)
Selected row background for data-state="active"
--table-active-color
var(--st-highlight-foreground)
Selected row text color
The cell paint chain reads --table-bg-state (hover, active) over --table-bg-type (variant, striped) over --table-bg. Variants set -type and runtime states set -state, so hover lights a striped row without erasing the stripe underneath.
9/mo</td>
</tr>
<tr>
<th scope="row">Business</th>
<td>50</td>
<td>1 TB</td>
<td class="text-end">$99/mo</td>
</tr>
<tr>
<th scope="row">Enterprise</th>
<td>Unlimited</td>
<td>Custom</td>
<td class="text-end">Talk to sales</td>
</tr>
</tbody>
</table>Row variants
Add .table__row--{intent} to a <tr> to call out a row that needs attention. Below is a job queue where one job is mid-retry and another has failed. Available intents are primary, success, info, warning, danger, plus neutral for a quiet emphasis.
| Job | Schedule | Last run | Duration |
|---|---|---|---|
| nightly-backup | Daily 02:00 UTC | 4 hours ago | 3m 12s |
| events-rollup | Hourly :00 | Retrying (2 of 5) | — |
| invoice-export | Mon 06:00 UTC | Yesterday | 1m 04s |
| webhook-replay | Every 15 min | Failed · 12 min ago | — |
| metrics-aggregate | Every 5 min | 2 min ago | 7s |
<table class="table table--align-middle">
<thead>
<tr>
<th scope="col">Job</th>
<th scope="col">Schedule</th>
<th scope="col">Last run</th>
<th scope="col" class="text-end">Duration</th>
</tr>
</thead>
<tbody>
<tr>
<td>nightly-backup</td>
<td>Daily 02:00 UTC</td>
<td>4 hours ago</td>
<td class="text-end">3m 12s</td>
</tr>
<tr class="table__row--warning">
<td>events-rollup</td>
<td>Hourly :00</td>
<td>Retrying (2 of 5)</td>
<td class="text-end">—</td>
</tr>
<tr>
<td>invoice-export</td>
<td>Mon 06:00 UTC</td>
<td>Yesterday</td>
<td class="text-end">1m 04s</td>
</tr>
<tr class="table__row--danger">
<td>webhook-replay</td>
<td>Every 15 min</td>
<td>Failed · 12 min ago</td>
<td class="text-end">—</td>
</tr>
<tr>
<td>metrics-aggregate</td>
<td>Every 5 min</td>
<td>2 min ago</td>
<td class="text-end">7s</td>
</tr>
</tbody>
</table>Striped rows
Add .table--striped to zebra-stripe every other row in the body.
| Region | Latency p50 | Latency p99 | Requests / min |
|---|---|---|---|
| us-east-1 | 42 ms | 180 ms | 12,840 |
| us-west-2 | 58 ms | 220 ms | 9,210 |
| eu-west-1 | 61 ms | 240 ms | 7,455 |
| ap-southeast-1 | 74 ms | 310 ms | 3,902 |
| sa-east-1 | 110 ms | 420 ms | 1,288 |
<table class="table table--striped">
<thead>
<tr>
<th scope="col">Region</th>
<th scope="col">Latency p50</th>
<th scope="col">Latency p99</th>
<th scope="col" class="text-end">Requests / min</th>
</tr>
</thead>
<tbody>
<tr><td>us-east-1</td><td>42 ms</td><td>180 ms</td><td class="text-end">12,840</td></tr>
<tr><td>us-west-2</td><td>58 ms</td><td>220 ms</td><td class="text-end">9,210</td></tr>
<tr><td>eu-west-1</td><td>61 ms</td><td>240 ms</td><td class="text-end">7,455</td></tr>
<tr><td>ap-southeast-1</td><td>74 ms</td><td>310 ms</td><td class="text-end">3,902</td></tr>
<tr><td>sa-east-1</td><td>110 ms</td><td>420 ms</td><td class="text-end">1,288</td></tr>
</tbody>
</table>Striped columns
Use .table--striped-cols when the table reads column-first.
| Quarter | Q1 | Q2 | Q3 | Q4 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| New accounts | 418 | 502 | 611 | 730 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Churned | 62 | 71 | 54 | 48 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Net new ARR | 82k |
TableA flat data grid for rows of structured records. BasicWrap your data in
Row variantsAdd
Striped rowsAdd
Striped columnsUse
Hoverable rowsAdd
Active rowFlag the persistent selection with
Bordered
Borderless
Small
Vertical alignmentCell content sits at the top by default. Add
CaptionA
Header altApply
With status badgesDrop a
With user avatarsStack an
With row actionsTrailing buttons go in the last cell with
Selectable rowsPut a
Pair with a header that surfaces the bulk count and actions once a row is checked. The count chip uses
2 of 4 selected
Inside a cardDrop the table straight into a
Deployments
Full dashboard tableComposes everything above. Alt-surface header, avatars in the first cell, badges for status, row actions trailing.
Team members
5 of 15 seats
Group dividerAdd
ResponsiveWrap the table in
For breakpoint-scoped scrolling, swap to CustomizationSeventeen variables retune Geometry
Head
Surface
Row states
The cell paint chain reads |
TableA flat data grid for rows of structured records. BasicWrap your data in
Row variantsAdd
Striped rowsAdd
Striped columnsUse
Hoverable rowsAdd
Active rowFlag the persistent selection with
Bordered
Borderless
Small
Vertical alignmentCell content sits at the top by default. Add
CaptionA
Header altApply
With status badgesDrop a
With user avatarsStack an
With row actionsTrailing buttons go in the last cell with
Selectable rowsPut a
Pair with a header that surfaces the bulk count and actions once a row is checked. The count chip uses
2 of 4 selected
Inside a cardDrop the table straight into a
Deployments
Full dashboard tableComposes everything above. Alt-surface header, avatars in the first cell, badges for status, row actions trailing.
Team members
5 of 15 seats
Group dividerAdd
ResponsiveWrap the table in
For breakpoint-scoped scrolling, swap to CustomizationSeventeen variables retune Geometry
Head
Surface
Row states
The cell paint chain reads | 31k |
<table class="table table--striped-cols">
<thead>
<tr>
<th scope="col">Quarter</th>
<th scope="col">Q1</th>
<th scope="col">Q2</th>
<th scope="col">Q3</th>
<th scope="col">Q4</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">New accounts</th>
<td>418</td><td>502</td><td>611</td><td>730</td>
</tr>
<tr>
<th scope="row">Churned</th>
<td>62</td><td>71</td><td>54</td><td>48</td>
</tr>
<tr>
<th scope="row">Net new ARR</th>
<td>82k</td><td>
Table
A flat data grid for rows of structured records.
Basic
Wrap your data in .table. The first and last column cells line up with the card body gutter so a table drops into a .card flush.
Plan
Seats
Storage
Price
Starter
3
10 GB
$0/mo
Team
15
100 GB
$29/mo
Business
50
1 TB
$99/mo
Enterprise
Unlimited
Custom
Talk to sales
<table class="table">
<thead>
<tr>
<th scope="col">Plan</th>
<th scope="col">Seats</th>
<th scope="col">Storage</th>
<th scope="col" class="text-end">Price</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">Starter</th>
<td>3</td>
<td>10 GB</td>
<td class="text-end">$0/mo</td>
</tr>
<tr>
<th scope="row">Team</th>
<td>15</td>
<td>100 GB</td>
<td class="text-end">$29/mo</td>
</tr>
<tr>
<th scope="row">Business</th>
<td>50</td>
<td>1 TB</td>
<td class="text-end">$99/mo</td>
</tr>
<tr>
<th scope="row">Enterprise</th>
<td>Unlimited</td>
<td>Custom</td>
<td class="text-end">Talk to sales</td>
</tr>
</tbody>
</table>
Row variants
Add .table__row--{intent} to a <tr> to call out a row that needs attention. Below is a job queue where one job is mid-retry and another has failed. Available intents are primary, success, info, warning, danger, plus neutral for a quiet emphasis.
Job
Schedule
Last run
Duration
nightly-backup
Daily 02:00 UTC
4 hours ago
3m 12s
events-rollup
Hourly :00
Retrying (2 of 5)
—
invoice-export
Mon 06:00 UTC
Yesterday
1m 04s
webhook-replay
Every 15 min
Failed · 12 min ago
—
metrics-aggregate
Every 5 min
2 min ago
7s
<table class="table table--align-middle">
<thead>
<tr>
<th scope="col">Job</th>
<th scope="col">Schedule</th>
<th scope="col">Last run</th>
<th scope="col" class="text-end">Duration</th>
</tr>
</thead>
<tbody>
<tr>
<td>nightly-backup</td>
<td>Daily 02:00 UTC</td>
<td>4 hours ago</td>
<td class="text-end">3m 12s</td>
</tr>
<tr class="table__row--warning">
<td>events-rollup</td>
<td>Hourly :00</td>
<td>Retrying (2 of 5)</td>
<td class="text-end">—</td>
</tr>
<tr>
<td>invoice-export</td>
<td>Mon 06:00 UTC</td>
<td>Yesterday</td>
<td class="text-end">1m 04s</td>
</tr>
<tr class="table__row--danger">
<td>webhook-replay</td>
<td>Every 15 min</td>
<td>Failed · 12 min ago</td>
<td class="text-end">—</td>
</tr>
<tr>
<td>metrics-aggregate</td>
<td>Every 5 min</td>
<td>2 min ago</td>
<td class="text-end">7s</td>
</tr>
</tbody>
</table>
Striped rows
Add .table--striped to zebra-stripe every other row in the body.
Region
Latency p50
Latency p99
Requests / min
us-east-1 42 ms 180 ms 12,840
us-west-2 58 ms 220 ms 9,210
eu-west-1 61 ms 240 ms 7,455
ap-southeast-1 74 ms 310 ms 3,902
sa-east-1 110 ms 420 ms 1,288
<table class="table table--striped">
<thead>
<tr>
<th scope="col">Region</th>
<th scope="col">Latency p50</th>
<th scope="col">Latency p99</th>
<th scope="col" class="text-end">Requests / min</th>
</tr>
</thead>
<tbody>
<tr><td>us-east-1</td><td>42 ms</td><td>180 ms</td><td class="text-end">12,840</td></tr>
<tr><td>us-west-2</td><td>58 ms</td><td>220 ms</td><td class="text-end">9,210</td></tr>
<tr><td>eu-west-1</td><td>61 ms</td><td>240 ms</td><td class="text-end">7,455</td></tr>
<tr><td>ap-southeast-1</td><td>74 ms</td><td>310 ms</td><td class="text-end">3,902</td></tr>
<tr><td>sa-east-1</td><td>110 ms</td><td>420 ms</td><td class="text-end">1,288</td></tr>
</tbody>
</table>
Striped columns
Use .table--striped-cols when the table reads column-first.
Quarter
Q1
Q2
Q3
Q4
New accounts
418 502 611 730
Churned
62 71 54 48
Net new ARR
$182k $214k $273k $331k
<table class="table table--striped-cols">
<thead>
<tr>
<th scope="col">Quarter</th>
<th scope="col">Q1</th>
<th scope="col">Q2</th>
<th scope="col">Q3</th>
<th scope="col">Q4</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">New accounts</th>
<td>418</td><td>502</td><td>611</td><td>730</td>
</tr>
<tr>
<th scope="row">Churned</th>
<td>62</td><td>71</td><td>54</td><td>48</td>
</tr>
<tr>
<th scope="row">Net new ARR</th>
<td>$182k</td><td>$214k</td><td>$273k</td><td>$331k</td>
</tr>
</tbody>
</table>
Hoverable rows
Add .table--hover to highlight the row under the cursor. Composes with .table--striped.
Endpoint
Method
Calls (24h)
Error rate
/v1/chargesPOST 48,210 0.12%
/v1/customersGET 31,089 0.04%
/v1/invoicesPOST 9,402 0.31%
/v1/webhooksPOST 2,118 1.84%
<table class="table table--hover">
<thead>
<tr>
<th scope="col">Endpoint</th>
<th scope="col">Method</th>
<th scope="col">Calls (24h)</th>
<th scope="col" class="text-end">Error rate</th>
</tr>
</thead>
<tbody>
<tr><td><code>/v1/charges</code></td><td>POST</td><td>48,210</td><td class="text-end">0.12%</td></tr>
<tr><td><code>/v1/customers</code></td><td>GET</td><td>31,089</td><td class="text-end">0.04%</td></tr>
<tr><td><code>/v1/invoices</code></td><td>POST</td><td>9,402</td><td class="text-end">0.31%</td></tr>
<tr><td><code>/v1/webhooks</code></td><td>POST</td><td>2,118</td><td class="text-end">1.84%</td></tr>
</tbody>
</table>
Active row
Flag the persistent selection with data-state="active" on the <tr>. Runs through the highlight tier, same as sidebar and dropdown selection.
Branch
Last commit
Author
Behind / Ahead
main Bump axios to 1.7.4 Mateo Reyes 0 / 0
feat/billing-v2 Add proration preview Maya Singh 0 / 14
fix/race-on-replay Drain queue before close Theo Wright 3 / 2
chore/upgrade-node-20 Pin engines field Sara Lin 6 / 1
<table class="table">
<thead>
<tr>
<th scope="col">Branch</th>
<th scope="col">Last commit</th>
<th scope="col">Author</th>
<th scope="col" class="text-end">Behind / Ahead</th>
</tr>
</thead>
<tbody>
<tr><td>main</td><td>Bump axios to 1.7.4</td><td>Mateo Reyes</td><td class="text-end">0 / 0</td></tr>
<tr data-state="active"><td>feat/billing-v2</td><td>Add proration preview</td><td>Maya Singh</td><td class="text-end">0 / 14</td></tr>
<tr><td>fix/race-on-replay</td><td>Drain queue before close</td><td>Theo Wright</td><td class="text-end">3 / 2</td></tr>
<tr><td>chore/upgrade-node-20</td><td>Pin engines field</td><td>Sara Lin</td><td class="text-end">6 / 1</td></tr>
</tbody>
</table>
Bordered
.table--bordered draws a border on every side of every cell.
Cluster
Nodes
CPU
Memory
prod-edge 24 62% 71%
prod-core 48 54% 68%
staging 6 18% 22%
<table class="table table--bordered">
<thead>
<tr>
<th scope="col">Cluster</th>
<th scope="col">Nodes</th>
<th scope="col">CPU</th>
<th scope="col">Memory</th>
</tr>
</thead>
<tbody>
<tr><td>prod-edge</td><td>24</td><td>62%</td><td>71%</td></tr>
<tr><td>prod-core</td><td>48</td><td>54%</td><td>68%</td></tr>
<tr><td>staging</td><td>6</td><td>18%</td><td>22%</td></tr>
</tbody>
</table>
Borderless
.table--borderless strips every row and cell border for a soft list look.
Project
Owner
Open issues
billing-service Maya Singh 12
auth-gateway Mateo Reyes 4
events-pipeline Sara Lin 7
web-dashboard Theo Wright 23
<table class="table table--borderless">
<thead>
<tr>
<th scope="col">Project</th>
<th scope="col">Owner</th>
<th scope="col" class="text-end">Open issues</th>
</tr>
</thead>
<tbody>
<tr><td>billing-service</td><td>Maya Singh</td><td class="text-end">12</td></tr>
<tr><td>auth-gateway</td><td>Mateo Reyes</td><td class="text-end">4</td></tr>
<tr><td>events-pipeline</td><td>Sara Lin</td><td class="text-end">7</td></tr>
<tr><td>web-dashboard</td><td>Theo Wright</td><td class="text-end">23</td></tr>
</tbody>
</table>
Small
.table--sm shrinks inner cell padding for denser rows. Edge padding stays so the flush-in-card alignment holds.
Key
Value
Last edited
SMTP_HOSTsmtp.postmark.io Maya · 3 days ago
SMTP_PORT587 Maya · 3 days ago
S3_BUCKETstisla-assets-prod Theo · last week
FEATURE_BILLING_V2true Mateo · 1 hour ago
RATE_LIMIT_RPM600 Sara · yesterday
<table class="table table--sm">
<thead>
<tr>
<th scope="col">Key</th>
<th scope="col">Value</th>
<th scope="col">Last edited</th>
</tr>
</thead>
<tbody>
<tr><td><code>SMTP_HOST</code></td><td>smtp.postmark.io</td><td>Maya · 3 days ago</td></tr>
<tr><td><code>SMTP_PORT</code></td><td>587</td><td>Maya · 3 days ago</td></tr>
<tr><td><code>S3_BUCKET</code></td><td>stisla-assets-prod</td><td>Theo · last week</td></tr>
<tr><td><code>FEATURE_BILLING_V2</code></td><td>true</td><td>Mateo · 1 hour ago</td></tr>
<tr><td><code>RATE_LIMIT_RPM</code></td><td>600</td><td>Sara · yesterday</td></tr>
</tbody>
</table>
Vertical alignment
Cell content sits at the top by default. Add .table--align-middle when rows mix multi-line text with shorter values.
Document
Summary
Pages
Onboarding handbook
Day-1 setup, IT accounts, payroll forms, and the company-wide reading list new hires work through in their first week.
42
Security policy
Device requirements, password rules, incident reporting, and the quarterly review schedule.
18
Engineering RFCs
Active proposals up for review this cycle. Each row links to the long-form doc.
7
<table class="table table--align-middle">
<thead>
<tr>
<th scope="col">Document</th>
<th scope="col">Summary</th>
<th scope="col" class="text-end">Pages</th>
</tr>
</thead>
<tbody>
<tr>
<td>Onboarding handbook</td>
<td>Day-1 setup, IT accounts, payroll forms, and the company-wide reading list new hires work through in their first week.</td>
<td class="text-end">42</td>
</tr>
<tr>
<td>Security policy</td>
<td>Device requirements, password rules, incident reporting, and the quarterly review schedule.</td>
<td class="text-end">18</td>
</tr>
<tr>
<td>Engineering RFCs</td>
<td>Active proposals up for review this cycle. Each row links to the long-form doc.</td>
<td class="text-end">7</td>
</tr>
</tbody>
</table>
Caption
A <caption> sits below the table and reads like a footnote. Promote it above the table with .caption-top.
Last refreshed at 09:42 UTC
Job
Schedule
Last run
weekly-invoice-rollup Mon 02:00 UTC 2 days ago · succeeded
nightly-vacuum Daily 03:30 UTC 6 hours ago · succeeded
hourly-replay Hourly :15 27 min ago · failed
<table class="table">
<caption>Last refreshed at 09:42 UTC</caption>
<thead>
<tr>
<th scope="col">Job</th>
<th scope="col">Schedule</th>
<th scope="col">Last run</th>
</tr>
</thead>
<tbody>
<tr><td>weekly-invoice-rollup</td><td>Mon 02:00 UTC</td><td>2 days ago · succeeded</td></tr>
<tr><td>nightly-vacuum</td><td>Daily 03:30 UTC</td><td>6 hours ago · succeeded</td></tr>
<tr><td>hourly-replay</td><td>Hourly :15</td><td>27 min ago · failed</td></tr>
</tbody>
</table>
Header alt
Apply .table__head--alt on the <thead> to opt the header row onto the alt surface. Same two-tone language as .card__header--alt.
Customer
Plan
MRR
Renews
Acme Corp Business $1,490 Aug 12
Riverway Ltd Team $580 Sep 04
Northwind Enterprise $8,200 Oct 22
Globex Team $580 Nov 09
<table class="table">
<thead class="table__head--alt">
<tr>
<th scope="col">Customer</th>
<th scope="col">Plan</th>
<th scope="col">MRR</th>
<th scope="col" class="text-end">Renews</th>
</tr>
</thead>
<tbody>
<tr><td>Acme Corp</td><td>Business</td><td>$1,490</td><td class="text-end">Aug 12</td></tr>
<tr><td>Riverway Ltd</td><td>Team</td><td>$580</td><td class="text-end">Sep 04</td></tr>
<tr><td>Northwind</td><td>Enterprise</td><td>$8,200</td><td class="text-end">Oct 22</td></tr>
<tr><td>Globex</td><td>Team</td><td>$580</td><td class="text-end">Nov 09</td></tr>
</tbody>
</table>
With status badges
Drop a .badge into a cell to flag state. Soft .badge--soft variants read cleaner inside a dense row than solid fills.
Invoice
Client
Amount
Due
Status
INV-1042
Acme Corp
$4,800.00
Jun 15
Sent
INV-1041
Riverway Ltd
$1,250.00
Jun 10
Paid
INV-1040
Northwind
$9,310.00
May 28
Overdue
INV-1039
Globex
$2,140.00
Jun 22
Draft
INV-1038
Initech
$680.00
Jun 30
Scheduled
<table class="table table--hover table--align-middle">
<thead class="table__head--alt">
<tr>
<th scope="col">Invoice</th>
<th scope="col">Client</th>
<th scope="col">Amount</th>
<th scope="col">Due</th>
<th scope="col">Status</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>INV-1042</code></td>
<td>Acme Corp</td>
<td>$4,800.00</td>
<td>Jun 15</td>
<td><span class="badge badge--soft badge--info"><i data-lucide="send"></i> Sent</span></td>
</tr>
<tr>
<td><code>INV-1041</code></td>
<td>Riverway Ltd</td>
<td>$1,250.00</td>
<td>Jun 10</td>
<td><span class="badge badge--soft badge--success"><i data-lucide="check"></i> Paid</span></td>
</tr>
<tr>
<td><code>INV-1040</code></td>
<td>Northwind</td>
<td>$9,310.00</td>
<td>May 28</td>
<td><span class="badge badge--soft badge--danger"><i data-lucide="alert-triangle"></i> Overdue</span></td>
</tr>
<tr>
<td><code>INV-1039</code></td>
<td>Globex</td>
<td>$2,140.00</td>
<td>Jun 22</td>
<td><span class="badge badge--soft badge--warning"><i data-lucide="clock"></i> Draft</span></td>
</tr>
<tr>
<td><code>INV-1038</code></td>
<td>Initech</td>
<td>$680.00</td>
<td>Jun 30</td>
<td><span class="badge badge--soft">Scheduled</span></td>
</tr>
</tbody>
</table>
With user avatars
Stack an <img> and a <div> in a flex cell to pair an avatar with a name and a secondary line. The image keeps its natural shape, so set border-radius: 50% and a width attribute.
Member
Role
Joined
Status
Maya Singh
maya@acme.co
Admin
Jan 2024
Active
Mateo Reyes
mateo@acme.co
Editor
Mar 2024
Active
Sara Lin
sara@acme.co
Viewer
May 2024
Invite pending
Theo Wright
theo@acme.co
Editor
Jun 2023
Suspended
<table class="table table--align-middle">
<thead class="table__head--alt">
<tr>
<th scope="col">Member</th>
<th scope="col">Role</th>
<th scope="col">Joined</th>
<th scope="col">Status</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<div class="d-flex align-items-center gap-2">
<img src="https://images.unsplash.com/photo-1494790108377-be9c29b29330?auto=format&fit=facearea&facepad=2.5&w=120&h=120&q=80" alt="" width="32" height="32" style="border-radius: 50%;" />
<div>
<div>Maya Singh</div>
<div class="text-muted-foreground">maya@acme.co</div>
</div>
</div>
</td>
<td>Admin</td>
<td>Jan 2024</td>
<td><span class="badge badge--soft badge--success">Active</span></td>
</tr>
<tr>
<td>
<div class="d-flex align-items-center gap-2">
<img src="https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?auto=format&fit=facearea&facepad=2.5&w=120&h=120&q=80" alt="" width="32" height="32" style="border-radius: 50%;" />
<div>
<div>Mateo Reyes</div>
<div class="text-muted-foreground">mateo@acme.co</div>
</div>
</div>
</td>
<td>Editor</td>
<td>Mar 2024</td>
<td><span class="badge badge--soft badge--success">Active</span></td>
</tr>
<tr>
<td>
<div class="d-flex align-items-center gap-2">
<img src="https://images.unsplash.com/photo-1438761681033-6461ffad8d80?auto=format&fit=facearea&facepad=2.5&w=120&h=120&q=80" alt="" width="32" height="32" style="border-radius: 50%;" />
<div>
<div>Sara Lin</div>
<div class="text-muted-foreground">sara@acme.co</div>
</div>
</div>
</td>
<td>Viewer</td>
<td>May 2024</td>
<td><span class="badge badge--soft badge--warning">Invite pending</span></td>
</tr>
<tr>
<td>
<div class="d-flex align-items-center gap-2">
<img src="https://images.unsplash.com/photo-1500648767791-00dcc994a43e?auto=format&fit=facearea&facepad=2.5&w=120&h=120&q=80" alt="" width="32" height="32" style="border-radius: 50%;" />
<div>
<div>Theo Wright</div>
<div class="text-muted-foreground">theo@acme.co</div>
</div>
</div>
</td>
<td>Editor</td>
<td>Jun 2023</td>
<td><span class="badge badge--soft badge--danger">Suspended</span></td>
</tr>
</tbody>
</table>
With row actions
Trailing buttons go in the last cell with .text-end. Ghost icon buttons sit quiet until the row is hovered.
API key
Scope
Created
Last used
Actions
sk_live_•••••8a1c
Live
Apr 12, 2024
2 mins ago
sk_test_•••••3f02
Test
Jan 03, 2024
1 hour ago
sk_live_•••••c4e9
Live
Nov 28, 2023
3 days ago
<table class="table table--hover table--align-middle">
<thead class="table__head--alt">
<tr>
<th scope="col">API key</th>
<th scope="col">Scope</th>
<th scope="col">Created</th>
<th scope="col">Last used</th>
<th scope="col" class="text-end">Actions</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>sk_live_•••••8a1c</code></td>
<td><span class="badge badge--soft badge--primary">Live</span></td>
<td>Apr 12, 2024</td>
<td>2 mins ago</td>
<td class="text-end">
<div class="d-inline-flex gap-1">
<button type="button" class="btn btn--sm btn--ghost btn--neutral btn--icon-only" aria-label="Copy">
<i data-lucide="copy"></i>
</button>
<button type="button" class="btn btn--sm btn--ghost btn--neutral btn--icon-only" aria-label="Revoke">
<i data-lucide="trash-2"></i>
</button>
</div>
</td>
</tr>
<tr>
<td><code>sk_test_•••••3f02</code></td>
<td><span class="badge badge--soft">Test</span></td>
<td>Jan 03, 2024</td>
<td>1 hour ago</td>
<td class="text-end">
<div class="d-inline-flex gap-1">
<button type="button" class="btn btn--sm btn--ghost btn--neutral btn--icon-only" aria-label="Copy">
<i data-lucide="copy"></i>
</button>
<button type="button" class="btn btn--sm btn--ghost btn--neutral btn--icon-only" aria-label="Revoke">
<i data-lucide="trash-2"></i>
</button>
</div>
</td>
</tr>
<tr>
<td><code>sk_live_•••••c4e9</code></td>
<td><span class="badge badge--soft badge--primary">Live</span></td>
<td>Nov 28, 2023</td>
<td>3 days ago</td>
<td class="text-end">
<div class="d-inline-flex gap-1">
<button type="button" class="btn btn--sm btn--ghost btn--neutral btn--icon-only" aria-label="Copy">
<i data-lucide="copy"></i>
</button>
<button type="button" class="btn btn--sm btn--ghost btn--neutral btn--icon-only" aria-label="Revoke">
<i data-lucide="trash-2"></i>
</button>
</div>
</td>
</tr>
</tbody>
</table>
Selectable rows
Put a .checkbox in the first column to let people pick rows. The header checkbox is the bulk toggle; flip data-state="active" on the row to mark it selected. The data-demo-select-rows hook on the table wires the live behavior.
Email
Source
Subscribed
Status
maya@acme.co
Landing page
Jun 02
Confirmed
mateo@acme.co
Referral · Sara
Jun 01
Confirmed
sara@acme.co
Webinar · Q2 launch
May 28
Pending
theo@acme.co
Import · Mailchimp
May 22
Unsubscribed
alex@acme.co
Landing page
May 19
Confirmed
<table class="table table--hover table--align-middle" data-demo-select-rows>
<thead class="table__head--alt">
<tr>
<th scope="col" style="width: 1rem;">
<input class="checkbox" type="checkbox" aria-label="Select all subscribers" />
</th>
<th scope="col">Email</th>
<th scope="col">Source</th>
<th scope="col">Subscribed</th>
<th scope="col">Status</th>
</tr>
</thead>
<tbody>
<tr>
<td><input class="checkbox" type="checkbox" aria-label="Select maya@acme.co" /></td>
<td>maya@acme.co</td>
<td>Landing page</td>
<td>Jun 02</td>
<td><span class="badge badge--soft badge--success">Confirmed</span></td>
</tr>
<tr data-state="active">
<td><input class="checkbox" type="checkbox" aria-label="Select mateo@acme.co" checked /></td>
<td>mateo@acme.co</td>
<td>Referral · Sara</td>
<td>Jun 01</td>
<td><span class="badge badge--soft badge--success">Confirmed</span></td>
</tr>
<tr data-state="active">
<td><input class="checkbox" type="checkbox" aria-label="Select sara@acme.co" checked /></td>
<td>sara@acme.co</td>
<td>Webinar · Q2 launch</td>
<td>May 28</td>
<td><span class="badge badge--soft badge--warning">Pending</span></td>
</tr>
<tr>
<td><input class="checkbox" type="checkbox" aria-label="Select theo@acme.co" /></td>
<td>theo@acme.co</td>
<td>Import · Mailchimp</td>
<td>May 22</td>
<td><span class="badge badge--soft">Unsubscribed</span></td>
</tr>
<tr>
<td><input class="checkbox" type="checkbox" aria-label="Select alex@acme.co" /></td>
<td>alex@acme.co</td>
<td>Landing page</td>
<td>May 19</td>
<td><span class="badge badge--soft badge--success">Confirmed</span></td>
</tr>
</tbody>
</table>
Pair with a header that surfaces the bulk count and actions once a row is checked. The count chip uses data-demo-select-count so the script can update it as rows toggle.
2 of 4 selected
From
Subject
Received
Maya Singh
Re: Q3 roadmap draft
9:42 AM
Mateo Reyes
Auth migration status
8:18 AM
Sara Lin
Pipeline retry logic
Yesterday
Theo Wright
Vendor renewal · Jun 30
Yesterday
<div class="card">
<div class="card__header card__header--alt">
<span><strong data-demo-select-count>2</strong> of 4 selected</span>
<div class="d-flex gap-2 ms-auto">
<button type="button" class="btn btn--sm btn--outline btn--neutral">
<i data-lucide="tag"></i>
Add tag
</button>
<button type="button" class="btn btn--sm btn--outline btn--neutral">
<i data-lucide="archive"></i>
Archive
</button>
<button type="button" class="btn btn--sm btn--danger">
<i data-lucide="trash-2"></i>
Delete
</button>
</div>
</div>
<table class="table table--hover table--align-middle mb-0" data-demo-select-rows>
<thead>
<tr>
<th scope="col" style="width: 1rem;">
<input class="checkbox" type="checkbox" aria-label="Select all messages" />
</th>
<th scope="col">From</th>
<th scope="col">Subject</th>
<th scope="col" class="text-end">Received</th>
</tr>
</thead>
<tbody>
<tr data-state="active">
<td><input class="checkbox" type="checkbox" aria-label="Select message from Maya Singh" checked /></td>
<td>Maya Singh</td>
<td>Re: Q3 roadmap draft</td>
<td class="text-end">9:42 AM</td>
</tr>
<tr data-state="active">
<td><input class="checkbox" type="checkbox" aria-label="Select message from Mateo Reyes" checked /></td>
<td>Mateo Reyes</td>
<td>Auth migration status</td>
<td class="text-end">8:18 AM</td>
</tr>
<tr>
<td><input class="checkbox" type="checkbox" aria-label="Select message from Sara Lin" /></td>
<td>Sara Lin</td>
<td>Pipeline retry logic</td>
<td class="text-end">Yesterday</td>
</tr>
<tr>
<td><input class="checkbox" type="checkbox" aria-label="Select message from Theo Wright" /></td>
<td>Theo Wright</td>
<td>Vendor renewal · Jun 30</td>
<td class="text-end">Yesterday</td>
</tr>
</tbody>
</table>
</div>
Inside a card
Drop the table straight into a .card with margin-bottom: 0 so it sits flush. The edge cells line up with the card header's left padding.
Deployments
Service
Environment
Version
Deployed
Status
api
production
v2.14.0
3 min ago
Healthy
web
production
v3.41.2
12 min ago
Degraded
worker
staging
v0.8.1
1 hour ago
Healthy
api
staging
v2.15.0-rc1
just now
Deploying
<div class="card">
<div class="card__header card__header--alt">
Deployments
<button type="button" class="btn btn--sm btn--primary ms-auto">Deploy</button>
</div>
<table class="table table--hover table--align-middle mb-0">
<thead>
<tr>
<th scope="col">Service</th>
<th scope="col">Environment</th>
<th scope="col">Version</th>
<th scope="col">Deployed</th>
<th scope="col">Status</th>
</tr>
</thead>
<tbody>
<tr>
<td>api</td>
<td><span class="badge badge--soft">production</span></td>
<td><code>v2.14.0</code></td>
<td>3 min ago</td>
<td><span class="badge badge--soft badge--success"><i data-lucide="check-circle-2"></i> Healthy</span></td>
</tr>
<tr>
<td>web</td>
<td><span class="badge badge--soft">production</span></td>
<td><code>v3.41.2</code></td>
<td>12 min ago</td>
<td><span class="badge badge--soft badge--warning"><i data-lucide="triangle-alert"></i> Degraded</span></td>
</tr>
<tr>
<td>worker</td>
<td><span class="badge badge--soft">staging</span></td>
<td><code>v0.8.1</code></td>
<td>1 hour ago</td>
<td><span class="badge badge--soft badge--success"><i data-lucide="check-circle-2"></i> Healthy</span></td>
</tr>
<tr>
<td>api</td>
<td><span class="badge badge--soft">staging</span></td>
<td><code>v2.15.0-rc1</code></td>
<td>just now</td>
<td>
<span class="badge badge--soft badge--info">
<span class="spinner spinner--sm" role="status" aria-hidden="true"></span>
Deploying
</span>
</td>
</tr>
</tbody>
</table>
</div>
Full dashboard table
Composes everything above. Alt-surface header, avatars in the first cell, badges for status, row actions trailing.
Team members
5 of 15 seats
Member
Role
Last active
Status
Actions
Alex Park
alex@acme.co
Owner
just now
Active
Maya Singh
maya@acme.co
Admin
12 min ago
Active
Mateo Reyes
mateo@acme.co
Editor
2 hours ago
Active
Sara Lin
sara@acme.co
Viewer
never
Invite pending
Theo Wright
theo@acme.co
Editor
1 week ago
Suspended
<div class="card">
<div class="card__header card__header--alt">
Team members
<span class="badge badge--soft ms-2">5 of 15 seats</span>
<button type="button" class="btn btn--sm btn--primary ms-auto">
<i data-lucide="user-plus"></i>
Invite
</button>
</div>
<table class="table table--hover table--align-middle mb-0">
<thead>
<tr>
<th scope="col">Member</th>
<th scope="col">Role</th>
<th scope="col">Last active</th>
<th scope="col">Status</th>
<th scope="col" class="text-end">Actions</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<div class="d-flex align-items-center gap-2">
<img src="https://images.unsplash.com/photo-1535713875002-d1d0cf377fde?auto=format&fit=facearea&facepad=2.5&w=120&h=120&q=80" alt="" width="32" height="32" style="border-radius: 50%;" />
<div>
<div>Alex Park</div>
<div class="text-muted-foreground">alex@acme.co</div>
</div>
</div>
</td>
<td><span class="badge badge--soft badge--primary">Owner</span></td>
<td>just now</td>
<td><span class="badge badge--soft badge--success">Active</span></td>
<td class="text-end">
<button type="button" class="btn btn--sm btn--ghost btn--neutral btn--icon-only" aria-label="More">
<i data-lucide="more-horizontal"></i>
</button>
</td>
</tr>
<tr>
<td>
<div class="d-flex align-items-center gap-2">
<img src="https://images.unsplash.com/photo-1494790108377-be9c29b29330?auto=format&fit=facearea&facepad=2.5&w=120&h=120&q=80" alt="" width="32" height="32" style="border-radius: 50%;" />
<div>
<div>Maya Singh</div>
<div class="text-muted-foreground">maya@acme.co</div>
</div>
</div>
</td>
<td><span class="badge badge--soft badge--info">Admin</span></td>
<td>12 min ago</td>
<td><span class="badge badge--soft badge--success">Active</span></td>
<td class="text-end">
<button type="button" class="btn btn--sm btn--ghost btn--neutral btn--icon-only" aria-label="More">
<i data-lucide="more-horizontal"></i>
</button>
</td>
</tr>
<tr>
<td>
<div class="d-flex align-items-center gap-2">
<img src="https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?auto=format&fit=facearea&facepad=2.5&w=120&h=120&q=80" alt="" width="32" height="32" style="border-radius: 50%;" />
<div>
<div>Mateo Reyes</div>
<div class="text-muted-foreground">mateo@acme.co</div>
</div>
</div>
</td>
<td><span class="badge badge--soft">Editor</span></td>
<td>2 hours ago</td>
<td><span class="badge badge--soft badge--success">Active</span></td>
<td class="text-end">
<button type="button" class="btn btn--sm btn--ghost btn--neutral btn--icon-only" aria-label="More">
<i data-lucide="more-horizontal"></i>
</button>
</td>
</tr>
<tr>
<td>
<div class="d-flex align-items-center gap-2">
<img src="https://images.unsplash.com/photo-1438761681033-6461ffad8d80?auto=format&fit=facearea&facepad=2.5&w=120&h=120&q=80" alt="" width="32" height="32" style="border-radius: 50%;" />
<div>
<div>Sara Lin</div>
<div class="text-muted-foreground">sara@acme.co</div>
</div>
</div>
</td>
<td><span class="badge badge--soft">Viewer</span></td>
<td>never</td>
<td><span class="badge badge--soft badge--warning">Invite pending</span></td>
<td class="text-end">
<button type="button" class="btn btn--sm btn--ghost btn--neutral btn--icon-only" aria-label="More">
<i data-lucide="more-horizontal"></i>
</button>
</td>
</tr>
<tr>
<td>
<div class="d-flex align-items-center gap-2">
<img src="https://images.unsplash.com/photo-1500648767791-00dcc994a43e?auto=format&fit=facearea&facepad=2.5&w=120&h=120&q=80" alt="" width="32" height="32" style="border-radius: 50%;" />
<div>
<div>Theo Wright</div>
<div class="text-muted-foreground">theo@acme.co</div>
</div>
</div>
</td>
<td><span class="badge badge--soft">Editor</span></td>
<td>1 week ago</td>
<td><span class="badge badge--soft badge--danger">Suspended</span></td>
<td class="text-end">
<button type="button" class="btn btn--sm btn--ghost btn--neutral btn--icon-only" aria-label="More">
<i data-lucide="more-horizontal"></i>
</button>
</td>
</tr>
</tbody>
</table>
</div>
Group divider
Add .table__body--divider to a <tbody> for a heavier rule above it. Helpful when the head reads as a column label rather than a section break.
Item
Qty
Line total
Pro seat license 15 $435.00
Storage add-on 2 $40.00
Priority support 1 $199.00
<table class="table">
<thead>
<tr>
<th scope="col">Item</th>
<th scope="col">Qty</th>
<th scope="col" class="text-end">Line total</th>
</tr>
</thead>
<tbody class="table__body--divider">
<tr><td>Pro seat license</td><td>15</td><td class="text-end">$435.00</td></tr>
<tr><td>Storage add-on</td><td>2</td><td class="text-end">$40.00</td></tr>
<tr><td>Priority support</td><td>1</td><td class="text-end">$199.00</td></tr>
</tbody>
</table>
Responsive
Wrap the table in .table-responsive when it might overflow narrow viewports. The wrapper scrolls horizontally; the table itself stays unchanged.
Customer
Plan
Seats
MRR
Started
Trial ends
Renews
Owner
Status
Acme Corp Business 48 $1,490
Feb 02, 2024 — Aug 12 Maya Singh
Active
Riverway Ltd Team 14 $580
May 14, 2024 — Sep 04 Mateo Reyes
Active
Northwind Enterprise 112 $8,200
Aug 30, 2022 — Oct 22 Alex Park
Active
Globex Team 9 $580
Jun 01, 2024 Jun 15 — Sara Lin
Trialing
Initech Starter 3 $0
Jun 03, 2024 — — Theo Wright
Free
<div class="table-responsive">
<table class="table">
<thead class="table__head--alt">
<tr>
<th scope="col">Customer</th>
<th scope="col">Plan</th>
<th scope="col">Seats</th>
<th scope="col">MRR</th>
<th scope="col">Started</th>
<th scope="col">Trial ends</th>
<th scope="col">Renews</th>
<th scope="col">Owner</th>
<th scope="col">Status</th>
</tr>
</thead>
<tbody>
<tr>
<td>Acme Corp</td><td>Business</td><td>48</td><td>$1,490</td>
<td>Feb 02, 2024</td><td>—</td><td>Aug 12</td><td>Maya Singh</td>
<td><span class="badge badge--soft badge--success">Active</span></td>
</tr>
<tr>
<td>Riverway Ltd</td><td>Team</td><td>14</td><td>$580</td>
<td>May 14, 2024</td><td>—</td><td>Sep 04</td><td>Mateo Reyes</td>
<td><span class="badge badge--soft badge--success">Active</span></td>
</tr>
<tr>
<td>Northwind</td><td>Enterprise</td><td>112</td><td>$8,200</td>
<td>Aug 30, 2022</td><td>—</td><td>Oct 22</td><td>Alex Park</td>
<td><span class="badge badge--soft badge--success">Active</span></td>
</tr>
<tr>
<td>Globex</td><td>Team</td><td>9</td><td>$580</td>
<td>Jun 01, 2024</td><td>Jun 15</td><td>—</td><td>Sara Lin</td>
<td><span class="badge badge--soft badge--warning">Trialing</span></td>
</tr>
<tr>
<td>Initech</td><td>Starter</td><td>3</td><td>$0</td>
<td>Jun 03, 2024</td><td>—</td><td>—</td><td>Theo Wright</td>
<td><span class="badge badge--soft">Free</span></td>
</tr>
</tbody>
</table>
</div>
For breakpoint-scoped scrolling, swap to .table-responsive-{sm|md|lg|xl|xxl}. The wrapper switches between scroll and natural layout at the named breakpoint.
Customization
Seventeen variables retune .table without touching component CSS. Override on .table itself, on a parent scope, or on :root, and the cascade scopes the change.
Geometry
Variable Default Use
--table-cell-padding-y
calc(0.75rem * var(--st-density))
Vertical cell padding
--table-cell-padding-x
calc(0.75rem * var(--st-density))
Horizontal cell padding
--table-cell-padding-sm
calc(0.25rem * var(--st-density))
Cell padding under .table--sm
--table-edge-padding
calc(1.25rem * var(--st-density))
First and last column inset, lines up with the card body gutter
--table-group-divider-width
2px
Rule above .table__body--divider
Head
Variable Default Use
--table-head-font-size
0.75rem
Header label size
--table-head-font-weight
500
Header label weight (down from browser-default 700)
--table-head-color
var(--st-muted-foreground)
Header label color
--table-head-bg-alt
var(--st-surface-2)
Fill applied by .table__head--alt
Surface
Variable Default Use
--table-color
var(--st-foreground)
Body text color
--table-bg
transparent
Cell background (rest layer)
--table-border-color
var(--st-border)
Row and cell border color
Row states
Variable Default Use
--table-striped-bg
color-mix(in oklch, var(--st-foreground) 4%, transparent)
Odd row / even column tint
--table-striped-color
var(--st-foreground)
Striped row text color
--table-hover-bg
var(--st-accent)
Row hover background under .table--hover
--table-hover-color
var(--st-accent-foreground)
Row hover text color
--table-active-bg
var(--st-highlight)
Selected row background for data-state="active"
--table-active-color
var(--st-highlight-foreground)
Selected row text color
The cell paint chain reads --table-bg-state (hover, active) over --table-bg-type (variant, striped) over --table-bg. Variants set -type and runtime states set -state, so hover lights a striped row without erasing the stripe underneath.
14k</td><td>
Table
A flat data grid for rows of structured records.
Basic
Wrap your data in .table. The first and last column cells line up with the card body gutter so a table drops into a .card flush.
Plan
Seats
Storage
Price
Starter
3
10 GB
$0/mo
Team
15
100 GB
$29/mo
Business
50
1 TB
$99/mo
Enterprise
Unlimited
Custom
Talk to sales
<table class="table">
<thead>
<tr>
<th scope="col">Plan</th>
<th scope="col">Seats</th>
<th scope="col">Storage</th>
<th scope="col" class="text-end">Price</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">Starter</th>
<td>3</td>
<td>10 GB</td>
<td class="text-end">$0/mo</td>
</tr>
<tr>
<th scope="row">Team</th>
<td>15</td>
<td>100 GB</td>
<td class="text-end">$29/mo</td>
</tr>
<tr>
<th scope="row">Business</th>
<td>50</td>
<td>1 TB</td>
<td class="text-end">$99/mo</td>
</tr>
<tr>
<th scope="row">Enterprise</th>
<td>Unlimited</td>
<td>Custom</td>
<td class="text-end">Talk to sales</td>
</tr>
</tbody>
</table>
Row variants
Add .table__row--{intent} to a <tr> to call out a row that needs attention. Below is a job queue where one job is mid-retry and another has failed. Available intents are primary, success, info, warning, danger, plus neutral for a quiet emphasis.
Job
Schedule
Last run
Duration
nightly-backup
Daily 02:00 UTC
4 hours ago
3m 12s
events-rollup
Hourly :00
Retrying (2 of 5)
—
invoice-export
Mon 06:00 UTC
Yesterday
1m 04s
webhook-replay
Every 15 min
Failed · 12 min ago
—
metrics-aggregate
Every 5 min
2 min ago
7s
<table class="table table--align-middle">
<thead>
<tr>
<th scope="col">Job</th>
<th scope="col">Schedule</th>
<th scope="col">Last run</th>
<th scope="col" class="text-end">Duration</th>
</tr>
</thead>
<tbody>
<tr>
<td>nightly-backup</td>
<td>Daily 02:00 UTC</td>
<td>4 hours ago</td>
<td class="text-end">3m 12s</td>
</tr>
<tr class="table__row--warning">
<td>events-rollup</td>
<td>Hourly :00</td>
<td>Retrying (2 of 5)</td>
<td class="text-end">—</td>
</tr>
<tr>
<td>invoice-export</td>
<td>Mon 06:00 UTC</td>
<td>Yesterday</td>
<td class="text-end">1m 04s</td>
</tr>
<tr class="table__row--danger">
<td>webhook-replay</td>
<td>Every 15 min</td>
<td>Failed · 12 min ago</td>
<td class="text-end">—</td>
</tr>
<tr>
<td>metrics-aggregate</td>
<td>Every 5 min</td>
<td>2 min ago</td>
<td class="text-end">7s</td>
</tr>
</tbody>
</table>
Striped rows
Add .table--striped to zebra-stripe every other row in the body.
Region
Latency p50
Latency p99
Requests / min
us-east-1 42 ms 180 ms 12,840
us-west-2 58 ms 220 ms 9,210
eu-west-1 61 ms 240 ms 7,455
ap-southeast-1 74 ms 310 ms 3,902
sa-east-1 110 ms 420 ms 1,288
<table class="table table--striped">
<thead>
<tr>
<th scope="col">Region</th>
<th scope="col">Latency p50</th>
<th scope="col">Latency p99</th>
<th scope="col" class="text-end">Requests / min</th>
</tr>
</thead>
<tbody>
<tr><td>us-east-1</td><td>42 ms</td><td>180 ms</td><td class="text-end">12,840</td></tr>
<tr><td>us-west-2</td><td>58 ms</td><td>220 ms</td><td class="text-end">9,210</td></tr>
<tr><td>eu-west-1</td><td>61 ms</td><td>240 ms</td><td class="text-end">7,455</td></tr>
<tr><td>ap-southeast-1</td><td>74 ms</td><td>310 ms</td><td class="text-end">3,902</td></tr>
<tr><td>sa-east-1</td><td>110 ms</td><td>420 ms</td><td class="text-end">1,288</td></tr>
</tbody>
</table>
Striped columns
Use .table--striped-cols when the table reads column-first.
Quarter
Q1
Q2
Q3
Q4
New accounts
418 502 611 730
Churned
62 71 54 48
Net new ARR
$182k $214k $273k $331k
<table class="table table--striped-cols">
<thead>
<tr>
<th scope="col">Quarter</th>
<th scope="col">Q1</th>
<th scope="col">Q2</th>
<th scope="col">Q3</th>
<th scope="col">Q4</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">New accounts</th>
<td>418</td><td>502</td><td>611</td><td>730</td>
</tr>
<tr>
<th scope="row">Churned</th>
<td>62</td><td>71</td><td>54</td><td>48</td>
</tr>
<tr>
<th scope="row">Net new ARR</th>
<td>$182k</td><td>$214k</td><td>$273k</td><td>$331k</td>
</tr>
</tbody>
</table>
Hoverable rows
Add .table--hover to highlight the row under the cursor. Composes with .table--striped.
Endpoint
Method
Calls (24h)
Error rate
/v1/chargesPOST 48,210 0.12%
/v1/customersGET 31,089 0.04%
/v1/invoicesPOST 9,402 0.31%
/v1/webhooksPOST 2,118 1.84%
<table class="table table--hover">
<thead>
<tr>
<th scope="col">Endpoint</th>
<th scope="col">Method</th>
<th scope="col">Calls (24h)</th>
<th scope="col" class="text-end">Error rate</th>
</tr>
</thead>
<tbody>
<tr><td><code>/v1/charges</code></td><td>POST</td><td>48,210</td><td class="text-end">0.12%</td></tr>
<tr><td><code>/v1/customers</code></td><td>GET</td><td>31,089</td><td class="text-end">0.04%</td></tr>
<tr><td><code>/v1/invoices</code></td><td>POST</td><td>9,402</td><td class="text-end">0.31%</td></tr>
<tr><td><code>/v1/webhooks</code></td><td>POST</td><td>2,118</td><td class="text-end">1.84%</td></tr>
</tbody>
</table>
Active row
Flag the persistent selection with data-state="active" on the <tr>. Runs through the highlight tier, same as sidebar and dropdown selection.
Branch
Last commit
Author
Behind / Ahead
main Bump axios to 1.7.4 Mateo Reyes 0 / 0
feat/billing-v2 Add proration preview Maya Singh 0 / 14
fix/race-on-replay Drain queue before close Theo Wright 3 / 2
chore/upgrade-node-20 Pin engines field Sara Lin 6 / 1
<table class="table">
<thead>
<tr>
<th scope="col">Branch</th>
<th scope="col">Last commit</th>
<th scope="col">Author</th>
<th scope="col" class="text-end">Behind / Ahead</th>
</tr>
</thead>
<tbody>
<tr><td>main</td><td>Bump axios to 1.7.4</td><td>Mateo Reyes</td><td class="text-end">0 / 0</td></tr>
<tr data-state="active"><td>feat/billing-v2</td><td>Add proration preview</td><td>Maya Singh</td><td class="text-end">0 / 14</td></tr>
<tr><td>fix/race-on-replay</td><td>Drain queue before close</td><td>Theo Wright</td><td class="text-end">3 / 2</td></tr>
<tr><td>chore/upgrade-node-20</td><td>Pin engines field</td><td>Sara Lin</td><td class="text-end">6 / 1</td></tr>
</tbody>
</table>
Bordered
.table--bordered draws a border on every side of every cell.
Cluster
Nodes
CPU
Memory
prod-edge 24 62% 71%
prod-core 48 54% 68%
staging 6 18% 22%
<table class="table table--bordered">
<thead>
<tr>
<th scope="col">Cluster</th>
<th scope="col">Nodes</th>
<th scope="col">CPU</th>
<th scope="col">Memory</th>
</tr>
</thead>
<tbody>
<tr><td>prod-edge</td><td>24</td><td>62%</td><td>71%</td></tr>
<tr><td>prod-core</td><td>48</td><td>54%</td><td>68%</td></tr>
<tr><td>staging</td><td>6</td><td>18%</td><td>22%</td></tr>
</tbody>
</table>
Borderless
.table--borderless strips every row and cell border for a soft list look.
Project
Owner
Open issues
billing-service Maya Singh 12
auth-gateway Mateo Reyes 4
events-pipeline Sara Lin 7
web-dashboard Theo Wright 23
<table class="table table--borderless">
<thead>
<tr>
<th scope="col">Project</th>
<th scope="col">Owner</th>
<th scope="col" class="text-end">Open issues</th>
</tr>
</thead>
<tbody>
<tr><td>billing-service</td><td>Maya Singh</td><td class="text-end">12</td></tr>
<tr><td>auth-gateway</td><td>Mateo Reyes</td><td class="text-end">4</td></tr>
<tr><td>events-pipeline</td><td>Sara Lin</td><td class="text-end">7</td></tr>
<tr><td>web-dashboard</td><td>Theo Wright</td><td class="text-end">23</td></tr>
</tbody>
</table>
Small
.table--sm shrinks inner cell padding for denser rows. Edge padding stays so the flush-in-card alignment holds.
Key
Value
Last edited
SMTP_HOSTsmtp.postmark.io Maya · 3 days ago
SMTP_PORT587 Maya · 3 days ago
S3_BUCKETstisla-assets-prod Theo · last week
FEATURE_BILLING_V2true Mateo · 1 hour ago
RATE_LIMIT_RPM600 Sara · yesterday
<table class="table table--sm">
<thead>
<tr>
<th scope="col">Key</th>
<th scope="col">Value</th>
<th scope="col">Last edited</th>
</tr>
</thead>
<tbody>
<tr><td><code>SMTP_HOST</code></td><td>smtp.postmark.io</td><td>Maya · 3 days ago</td></tr>
<tr><td><code>SMTP_PORT</code></td><td>587</td><td>Maya · 3 days ago</td></tr>
<tr><td><code>S3_BUCKET</code></td><td>stisla-assets-prod</td><td>Theo · last week</td></tr>
<tr><td><code>FEATURE_BILLING_V2</code></td><td>true</td><td>Mateo · 1 hour ago</td></tr>
<tr><td><code>RATE_LIMIT_RPM</code></td><td>600</td><td>Sara · yesterday</td></tr>
</tbody>
</table>
Vertical alignment
Cell content sits at the top by default. Add .table--align-middle when rows mix multi-line text with shorter values.
Document
Summary
Pages
Onboarding handbook
Day-1 setup, IT accounts, payroll forms, and the company-wide reading list new hires work through in their first week.
42
Security policy
Device requirements, password rules, incident reporting, and the quarterly review schedule.
18
Engineering RFCs
Active proposals up for review this cycle. Each row links to the long-form doc.
7
<table class="table table--align-middle">
<thead>
<tr>
<th scope="col">Document</th>
<th scope="col">Summary</th>
<th scope="col" class="text-end">Pages</th>
</tr>
</thead>
<tbody>
<tr>
<td>Onboarding handbook</td>
<td>Day-1 setup, IT accounts, payroll forms, and the company-wide reading list new hires work through in their first week.</td>
<td class="text-end">42</td>
</tr>
<tr>
<td>Security policy</td>
<td>Device requirements, password rules, incident reporting, and the quarterly review schedule.</td>
<td class="text-end">18</td>
</tr>
<tr>
<td>Engineering RFCs</td>
<td>Active proposals up for review this cycle. Each row links to the long-form doc.</td>
<td class="text-end">7</td>
</tr>
</tbody>
</table>
Caption
A <caption> sits below the table and reads like a footnote. Promote it above the table with .caption-top.
Last refreshed at 09:42 UTC
Job
Schedule
Last run
weekly-invoice-rollup Mon 02:00 UTC 2 days ago · succeeded
nightly-vacuum Daily 03:30 UTC 6 hours ago · succeeded
hourly-replay Hourly :15 27 min ago · failed
<table class="table">
<caption>Last refreshed at 09:42 UTC</caption>
<thead>
<tr>
<th scope="col">Job</th>
<th scope="col">Schedule</th>
<th scope="col">Last run</th>
</tr>
</thead>
<tbody>
<tr><td>weekly-invoice-rollup</td><td>Mon 02:00 UTC</td><td>2 days ago · succeeded</td></tr>
<tr><td>nightly-vacuum</td><td>Daily 03:30 UTC</td><td>6 hours ago · succeeded</td></tr>
<tr><td>hourly-replay</td><td>Hourly :15</td><td>27 min ago · failed</td></tr>
</tbody>
</table>
Header alt
Apply .table__head--alt on the <thead> to opt the header row onto the alt surface. Same two-tone language as .card__header--alt.
Customer
Plan
MRR
Renews
Acme Corp Business $1,490 Aug 12
Riverway Ltd Team $580 Sep 04
Northwind Enterprise $8,200 Oct 22
Globex Team $580 Nov 09
<table class="table">
<thead class="table__head--alt">
<tr>
<th scope="col">Customer</th>
<th scope="col">Plan</th>
<th scope="col">MRR</th>
<th scope="col" class="text-end">Renews</th>
</tr>
</thead>
<tbody>
<tr><td>Acme Corp</td><td>Business</td><td>$1,490</td><td class="text-end">Aug 12</td></tr>
<tr><td>Riverway Ltd</td><td>Team</td><td>$580</td><td class="text-end">Sep 04</td></tr>
<tr><td>Northwind</td><td>Enterprise</td><td>$8,200</td><td class="text-end">Oct 22</td></tr>
<tr><td>Globex</td><td>Team</td><td>$580</td><td class="text-end">Nov 09</td></tr>
</tbody>
</table>
With status badges
Drop a .badge into a cell to flag state. Soft .badge--soft variants read cleaner inside a dense row than solid fills.
Invoice
Client
Amount
Due
Status
INV-1042
Acme Corp
$4,800.00
Jun 15
Sent
INV-1041
Riverway Ltd
$1,250.00
Jun 10
Paid
INV-1040
Northwind
$9,310.00
May 28
Overdue
INV-1039
Globex
$2,140.00
Jun 22
Draft
INV-1038
Initech
$680.00
Jun 30
Scheduled
<table class="table table--hover table--align-middle">
<thead class="table__head--alt">
<tr>
<th scope="col">Invoice</th>
<th scope="col">Client</th>
<th scope="col">Amount</th>
<th scope="col">Due</th>
<th scope="col">Status</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>INV-1042</code></td>
<td>Acme Corp</td>
<td>$4,800.00</td>
<td>Jun 15</td>
<td><span class="badge badge--soft badge--info"><i data-lucide="send"></i> Sent</span></td>
</tr>
<tr>
<td><code>INV-1041</code></td>
<td>Riverway Ltd</td>
<td>$1,250.00</td>
<td>Jun 10</td>
<td><span class="badge badge--soft badge--success"><i data-lucide="check"></i> Paid</span></td>
</tr>
<tr>
<td><code>INV-1040</code></td>
<td>Northwind</td>
<td>$9,310.00</td>
<td>May 28</td>
<td><span class="badge badge--soft badge--danger"><i data-lucide="alert-triangle"></i> Overdue</span></td>
</tr>
<tr>
<td><code>INV-1039</code></td>
<td>Globex</td>
<td>$2,140.00</td>
<td>Jun 22</td>
<td><span class="badge badge--soft badge--warning"><i data-lucide="clock"></i> Draft</span></td>
</tr>
<tr>
<td><code>INV-1038</code></td>
<td>Initech</td>
<td>$680.00</td>
<td>Jun 30</td>
<td><span class="badge badge--soft">Scheduled</span></td>
</tr>
</tbody>
</table>
With user avatars
Stack an <img> and a <div> in a flex cell to pair an avatar with a name and a secondary line. The image keeps its natural shape, so set border-radius: 50% and a width attribute.
Member
Role
Joined
Status
Maya Singh
maya@acme.co
Admin
Jan 2024
Active
Mateo Reyes
mateo@acme.co
Editor
Mar 2024
Active
Sara Lin
sara@acme.co
Viewer
May 2024
Invite pending
Theo Wright
theo@acme.co
Editor
Jun 2023
Suspended
<table class="table table--align-middle">
<thead class="table__head--alt">
<tr>
<th scope="col">Member</th>
<th scope="col">Role</th>
<th scope="col">Joined</th>
<th scope="col">Status</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<div class="d-flex align-items-center gap-2">
<img src="https://images.unsplash.com/photo-1494790108377-be9c29b29330?auto=format&fit=facearea&facepad=2.5&w=120&h=120&q=80" alt="" width="32" height="32" style="border-radius: 50%;" />
<div>
<div>Maya Singh</div>
<div class="text-muted-foreground">maya@acme.co</div>
</div>
</div>
</td>
<td>Admin</td>
<td>Jan 2024</td>
<td><span class="badge badge--soft badge--success">Active</span></td>
</tr>
<tr>
<td>
<div class="d-flex align-items-center gap-2">
<img src="https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?auto=format&fit=facearea&facepad=2.5&w=120&h=120&q=80" alt="" width="32" height="32" style="border-radius: 50%;" />
<div>
<div>Mateo Reyes</div>
<div class="text-muted-foreground">mateo@acme.co</div>
</div>
</div>
</td>
<td>Editor</td>
<td>Mar 2024</td>
<td><span class="badge badge--soft badge--success">Active</span></td>
</tr>
<tr>
<td>
<div class="d-flex align-items-center gap-2">
<img src="https://images.unsplash.com/photo-1438761681033-6461ffad8d80?auto=format&fit=facearea&facepad=2.5&w=120&h=120&q=80" alt="" width="32" height="32" style="border-radius: 50%;" />
<div>
<div>Sara Lin</div>
<div class="text-muted-foreground">sara@acme.co</div>
</div>
</div>
</td>
<td>Viewer</td>
<td>May 2024</td>
<td><span class="badge badge--soft badge--warning">Invite pending</span></td>
</tr>
<tr>
<td>
<div class="d-flex align-items-center gap-2">
<img src="https://images.unsplash.com/photo-1500648767791-00dcc994a43e?auto=format&fit=facearea&facepad=2.5&w=120&h=120&q=80" alt="" width="32" height="32" style="border-radius: 50%;" />
<div>
<div>Theo Wright</div>
<div class="text-muted-foreground">theo@acme.co</div>
</div>
</div>
</td>
<td>Editor</td>
<td>Jun 2023</td>
<td><span class="badge badge--soft badge--danger">Suspended</span></td>
</tr>
</tbody>
</table>
With row actions
Trailing buttons go in the last cell with .text-end. Ghost icon buttons sit quiet until the row is hovered.
API key
Scope
Created
Last used
Actions
sk_live_•••••8a1c
Live
Apr 12, 2024
2 mins ago
sk_test_•••••3f02
Test
Jan 03, 2024
1 hour ago
sk_live_•••••c4e9
Live
Nov 28, 2023
3 days ago
<table class="table table--hover table--align-middle">
<thead class="table__head--alt">
<tr>
<th scope="col">API key</th>
<th scope="col">Scope</th>
<th scope="col">Created</th>
<th scope="col">Last used</th>
<th scope="col" class="text-end">Actions</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>sk_live_•••••8a1c</code></td>
<td><span class="badge badge--soft badge--primary">Live</span></td>
<td>Apr 12, 2024</td>
<td>2 mins ago</td>
<td class="text-end">
<div class="d-inline-flex gap-1">
<button type="button" class="btn btn--sm btn--ghost btn--neutral btn--icon-only" aria-label="Copy">
<i data-lucide="copy"></i>
</button>
<button type="button" class="btn btn--sm btn--ghost btn--neutral btn--icon-only" aria-label="Revoke">
<i data-lucide="trash-2"></i>
</button>
</div>
</td>
</tr>
<tr>
<td><code>sk_test_•••••3f02</code></td>
<td><span class="badge badge--soft">Test</span></td>
<td>Jan 03, 2024</td>
<td>1 hour ago</td>
<td class="text-end">
<div class="d-inline-flex gap-1">
<button type="button" class="btn btn--sm btn--ghost btn--neutral btn--icon-only" aria-label="Copy">
<i data-lucide="copy"></i>
</button>
<button type="button" class="btn btn--sm btn--ghost btn--neutral btn--icon-only" aria-label="Revoke">
<i data-lucide="trash-2"></i>
</button>
</div>
</td>
</tr>
<tr>
<td><code>sk_live_•••••c4e9</code></td>
<td><span class="badge badge--soft badge--primary">Live</span></td>
<td>Nov 28, 2023</td>
<td>3 days ago</td>
<td class="text-end">
<div class="d-inline-flex gap-1">
<button type="button" class="btn btn--sm btn--ghost btn--neutral btn--icon-only" aria-label="Copy">
<i data-lucide="copy"></i>
</button>
<button type="button" class="btn btn--sm btn--ghost btn--neutral btn--icon-only" aria-label="Revoke">
<i data-lucide="trash-2"></i>
</button>
</div>
</td>
</tr>
</tbody>
</table>
Selectable rows
Put a .checkbox in the first column to let people pick rows. The header checkbox is the bulk toggle; flip data-state="active" on the row to mark it selected. The data-demo-select-rows hook on the table wires the live behavior.
Email
Source
Subscribed
Status
maya@acme.co
Landing page
Jun 02
Confirmed
mateo@acme.co
Referral · Sara
Jun 01
Confirmed
sara@acme.co
Webinar · Q2 launch
May 28
Pending
theo@acme.co
Import · Mailchimp
May 22
Unsubscribed
alex@acme.co
Landing page
May 19
Confirmed
<table class="table table--hover table--align-middle" data-demo-select-rows>
<thead class="table__head--alt">
<tr>
<th scope="col" style="width: 1rem;">
<input class="checkbox" type="checkbox" aria-label="Select all subscribers" />
</th>
<th scope="col">Email</th>
<th scope="col">Source</th>
<th scope="col">Subscribed</th>
<th scope="col">Status</th>
</tr>
</thead>
<tbody>
<tr>
<td><input class="checkbox" type="checkbox" aria-label="Select maya@acme.co" /></td>
<td>maya@acme.co</td>
<td>Landing page</td>
<td>Jun 02</td>
<td><span class="badge badge--soft badge--success">Confirmed</span></td>
</tr>
<tr data-state="active">
<td><input class="checkbox" type="checkbox" aria-label="Select mateo@acme.co" checked /></td>
<td>mateo@acme.co</td>
<td>Referral · Sara</td>
<td>Jun 01</td>
<td><span class="badge badge--soft badge--success">Confirmed</span></td>
</tr>
<tr data-state="active">
<td><input class="checkbox" type="checkbox" aria-label="Select sara@acme.co" checked /></td>
<td>sara@acme.co</td>
<td>Webinar · Q2 launch</td>
<td>May 28</td>
<td><span class="badge badge--soft badge--warning">Pending</span></td>
</tr>
<tr>
<td><input class="checkbox" type="checkbox" aria-label="Select theo@acme.co" /></td>
<td>theo@acme.co</td>
<td>Import · Mailchimp</td>
<td>May 22</td>
<td><span class="badge badge--soft">Unsubscribed</span></td>
</tr>
<tr>
<td><input class="checkbox" type="checkbox" aria-label="Select alex@acme.co" /></td>
<td>alex@acme.co</td>
<td>Landing page</td>
<td>May 19</td>
<td><span class="badge badge--soft badge--success">Confirmed</span></td>
</tr>
</tbody>
</table>
Pair with a header that surfaces the bulk count and actions once a row is checked. The count chip uses data-demo-select-count so the script can update it as rows toggle.
2 of 4 selected
From
Subject
Received
Maya Singh
Re: Q3 roadmap draft
9:42 AM
Mateo Reyes
Auth migration status
8:18 AM
Sara Lin
Pipeline retry logic
Yesterday
Theo Wright
Vendor renewal · Jun 30
Yesterday
<div class="card">
<div class="card__header card__header--alt">
<span><strong data-demo-select-count>2</strong> of 4 selected</span>
<div class="d-flex gap-2 ms-auto">
<button type="button" class="btn btn--sm btn--outline btn--neutral">
<i data-lucide="tag"></i>
Add tag
</button>
<button type="button" class="btn btn--sm btn--outline btn--neutral">
<i data-lucide="archive"></i>
Archive
</button>
<button type="button" class="btn btn--sm btn--danger">
<i data-lucide="trash-2"></i>
Delete
</button>
</div>
</div>
<table class="table table--hover table--align-middle mb-0" data-demo-select-rows>
<thead>
<tr>
<th scope="col" style="width: 1rem;">
<input class="checkbox" type="checkbox" aria-label="Select all messages" />
</th>
<th scope="col">From</th>
<th scope="col">Subject</th>
<th scope="col" class="text-end">Received</th>
</tr>
</thead>
<tbody>
<tr data-state="active">
<td><input class="checkbox" type="checkbox" aria-label="Select message from Maya Singh" checked /></td>
<td>Maya Singh</td>
<td>Re: Q3 roadmap draft</td>
<td class="text-end">9:42 AM</td>
</tr>
<tr data-state="active">
<td><input class="checkbox" type="checkbox" aria-label="Select message from Mateo Reyes" checked /></td>
<td>Mateo Reyes</td>
<td>Auth migration status</td>
<td class="text-end">8:18 AM</td>
</tr>
<tr>
<td><input class="checkbox" type="checkbox" aria-label="Select message from Sara Lin" /></td>
<td>Sara Lin</td>
<td>Pipeline retry logic</td>
<td class="text-end">Yesterday</td>
</tr>
<tr>
<td><input class="checkbox" type="checkbox" aria-label="Select message from Theo Wright" /></td>
<td>Theo Wright</td>
<td>Vendor renewal · Jun 30</td>
<td class="text-end">Yesterday</td>
</tr>
</tbody>
</table>
</div>
Inside a card
Drop the table straight into a .card with margin-bottom: 0 so it sits flush. The edge cells line up with the card header's left padding.
Deployments
Service
Environment
Version
Deployed
Status
api
production
v2.14.0
3 min ago
Healthy
web
production
v3.41.2
12 min ago
Degraded
worker
staging
v0.8.1
1 hour ago
Healthy
api
staging
v2.15.0-rc1
just now
Deploying
<div class="card">
<div class="card__header card__header--alt">
Deployments
<button type="button" class="btn btn--sm btn--primary ms-auto">Deploy</button>
</div>
<table class="table table--hover table--align-middle mb-0">
<thead>
<tr>
<th scope="col">Service</th>
<th scope="col">Environment</th>
<th scope="col">Version</th>
<th scope="col">Deployed</th>
<th scope="col">Status</th>
</tr>
</thead>
<tbody>
<tr>
<td>api</td>
<td><span class="badge badge--soft">production</span></td>
<td><code>v2.14.0</code></td>
<td>3 min ago</td>
<td><span class="badge badge--soft badge--success"><i data-lucide="check-circle-2"></i> Healthy</span></td>
</tr>
<tr>
<td>web</td>
<td><span class="badge badge--soft">production</span></td>
<td><code>v3.41.2</code></td>
<td>12 min ago</td>
<td><span class="badge badge--soft badge--warning"><i data-lucide="triangle-alert"></i> Degraded</span></td>
</tr>
<tr>
<td>worker</td>
<td><span class="badge badge--soft">staging</span></td>
<td><code>v0.8.1</code></td>
<td>1 hour ago</td>
<td><span class="badge badge--soft badge--success"><i data-lucide="check-circle-2"></i> Healthy</span></td>
</tr>
<tr>
<td>api</td>
<td><span class="badge badge--soft">staging</span></td>
<td><code>v2.15.0-rc1</code></td>
<td>just now</td>
<td>
<span class="badge badge--soft badge--info">
<span class="spinner spinner--sm" role="status" aria-hidden="true"></span>
Deploying
</span>
</td>
</tr>
</tbody>
</table>
</div>
Full dashboard table
Composes everything above. Alt-surface header, avatars in the first cell, badges for status, row actions trailing.
Team members
5 of 15 seats
Member
Role
Last active
Status
Actions
Alex Park
alex@acme.co
Owner
just now
Active
Maya Singh
maya@acme.co
Admin
12 min ago
Active
Mateo Reyes
mateo@acme.co
Editor
2 hours ago
Active
Sara Lin
sara@acme.co
Viewer
never
Invite pending
Theo Wright
theo@acme.co
Editor
1 week ago
Suspended
<div class="card">
<div class="card__header card__header--alt">
Team members
<span class="badge badge--soft ms-2">5 of 15 seats</span>
<button type="button" class="btn btn--sm btn--primary ms-auto">
<i data-lucide="user-plus"></i>
Invite
</button>
</div>
<table class="table table--hover table--align-middle mb-0">
<thead>
<tr>
<th scope="col">Member</th>
<th scope="col">Role</th>
<th scope="col">Last active</th>
<th scope="col">Status</th>
<th scope="col" class="text-end">Actions</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<div class="d-flex align-items-center gap-2">
<img src="https://images.unsplash.com/photo-1535713875002-d1d0cf377fde?auto=format&fit=facearea&facepad=2.5&w=120&h=120&q=80" alt="" width="32" height="32" style="border-radius: 50%;" />
<div>
<div>Alex Park</div>
<div class="text-muted-foreground">alex@acme.co</div>
</div>
</div>
</td>
<td><span class="badge badge--soft badge--primary">Owner</span></td>
<td>just now</td>
<td><span class="badge badge--soft badge--success">Active</span></td>
<td class="text-end">
<button type="button" class="btn btn--sm btn--ghost btn--neutral btn--icon-only" aria-label="More">
<i data-lucide="more-horizontal"></i>
</button>
</td>
</tr>
<tr>
<td>
<div class="d-flex align-items-center gap-2">
<img src="https://images.unsplash.com/photo-1494790108377-be9c29b29330?auto=format&fit=facearea&facepad=2.5&w=120&h=120&q=80" alt="" width="32" height="32" style="border-radius: 50%;" />
<div>
<div>Maya Singh</div>
<div class="text-muted-foreground">maya@acme.co</div>
</div>
</div>
</td>
<td><span class="badge badge--soft badge--info">Admin</span></td>
<td>12 min ago</td>
<td><span class="badge badge--soft badge--success">Active</span></td>
<td class="text-end">
<button type="button" class="btn btn--sm btn--ghost btn--neutral btn--icon-only" aria-label="More">
<i data-lucide="more-horizontal"></i>
</button>
</td>
</tr>
<tr>
<td>
<div class="d-flex align-items-center gap-2">
<img src="https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?auto=format&fit=facearea&facepad=2.5&w=120&h=120&q=80" alt="" width="32" height="32" style="border-radius: 50%;" />
<div>
<div>Mateo Reyes</div>
<div class="text-muted-foreground">mateo@acme.co</div>
</div>
</div>
</td>
<td><span class="badge badge--soft">Editor</span></td>
<td>2 hours ago</td>
<td><span class="badge badge--soft badge--success">Active</span></td>
<td class="text-end">
<button type="button" class="btn btn--sm btn--ghost btn--neutral btn--icon-only" aria-label="More">
<i data-lucide="more-horizontal"></i>
</button>
</td>
</tr>
<tr>
<td>
<div class="d-flex align-items-center gap-2">
<img src="https://images.unsplash.com/photo-1438761681033-6461ffad8d80?auto=format&fit=facearea&facepad=2.5&w=120&h=120&q=80" alt="" width="32" height="32" style="border-radius: 50%;" />
<div>
<div>Sara Lin</div>
<div class="text-muted-foreground">sara@acme.co</div>
</div>
</div>
</td>
<td><span class="badge badge--soft">Viewer</span></td>
<td>never</td>
<td><span class="badge badge--soft badge--warning">Invite pending</span></td>
<td class="text-end">
<button type="button" class="btn btn--sm btn--ghost btn--neutral btn--icon-only" aria-label="More">
<i data-lucide="more-horizontal"></i>
</button>
</td>
</tr>
<tr>
<td>
<div class="d-flex align-items-center gap-2">
<img src="https://images.unsplash.com/photo-1500648767791-00dcc994a43e?auto=format&fit=facearea&facepad=2.5&w=120&h=120&q=80" alt="" width="32" height="32" style="border-radius: 50%;" />
<div>
<div>Theo Wright</div>
<div class="text-muted-foreground">theo@acme.co</div>
</div>
</div>
</td>
<td><span class="badge badge--soft">Editor</span></td>
<td>1 week ago</td>
<td><span class="badge badge--soft badge--danger">Suspended</span></td>
<td class="text-end">
<button type="button" class="btn btn--sm btn--ghost btn--neutral btn--icon-only" aria-label="More">
<i data-lucide="more-horizontal"></i>
</button>
</td>
</tr>
</tbody>
</table>
</div>
Group divider
Add .table__body--divider to a <tbody> for a heavier rule above it. Helpful when the head reads as a column label rather than a section break.
Item
Qty
Line total
Pro seat license 15 $435.00
Storage add-on 2 $40.00
Priority support 1 $199.00
<table class="table">
<thead>
<tr>
<th scope="col">Item</th>
<th scope="col">Qty</th>
<th scope="col" class="text-end">Line total</th>
</tr>
</thead>
<tbody class="table__body--divider">
<tr><td>Pro seat license</td><td>15</td><td class="text-end">$435.00</td></tr>
<tr><td>Storage add-on</td><td>2</td><td class="text-end">$40.00</td></tr>
<tr><td>Priority support</td><td>1</td><td class="text-end">$199.00</td></tr>
</tbody>
</table>
Responsive
Wrap the table in .table-responsive when it might overflow narrow viewports. The wrapper scrolls horizontally; the table itself stays unchanged.
Customer
Plan
Seats
MRR
Started
Trial ends
Renews
Owner
Status
Acme Corp Business 48 $1,490
Feb 02, 2024 — Aug 12 Maya Singh
Active
Riverway Ltd Team 14 $580
May 14, 2024 — Sep 04 Mateo Reyes
Active
Northwind Enterprise 112 $8,200
Aug 30, 2022 — Oct 22 Alex Park
Active
Globex Team 9 $580
Jun 01, 2024 Jun 15 — Sara Lin
Trialing
Initech Starter 3 $0
Jun 03, 2024 — — Theo Wright
Free
<div class="table-responsive">
<table class="table">
<thead class="table__head--alt">
<tr>
<th scope="col">Customer</th>
<th scope="col">Plan</th>
<th scope="col">Seats</th>
<th scope="col">MRR</th>
<th scope="col">Started</th>
<th scope="col">Trial ends</th>
<th scope="col">Renews</th>
<th scope="col">Owner</th>
<th scope="col">Status</th>
</tr>
</thead>
<tbody>
<tr>
<td>Acme Corp</td><td>Business</td><td>48</td><td>$1,490</td>
<td>Feb 02, 2024</td><td>—</td><td>Aug 12</td><td>Maya Singh</td>
<td><span class="badge badge--soft badge--success">Active</span></td>
</tr>
<tr>
<td>Riverway Ltd</td><td>Team</td><td>14</td><td>$580</td>
<td>May 14, 2024</td><td>—</td><td>Sep 04</td><td>Mateo Reyes</td>
<td><span class="badge badge--soft badge--success">Active</span></td>
</tr>
<tr>
<td>Northwind</td><td>Enterprise</td><td>112</td><td>$8,200</td>
<td>Aug 30, 2022</td><td>—</td><td>Oct 22</td><td>Alex Park</td>
<td><span class="badge badge--soft badge--success">Active</span></td>
</tr>
<tr>
<td>Globex</td><td>Team</td><td>9</td><td>$580</td>
<td>Jun 01, 2024</td><td>Jun 15</td><td>—</td><td>Sara Lin</td>
<td><span class="badge badge--soft badge--warning">Trialing</span></td>
</tr>
<tr>
<td>Initech</td><td>Starter</td><td>3</td><td>$0</td>
<td>Jun 03, 2024</td><td>—</td><td>—</td><td>Theo Wright</td>
<td><span class="badge badge--soft">Free</span></td>
</tr>
</tbody>
</table>
</div>
For breakpoint-scoped scrolling, swap to .table-responsive-{sm|md|lg|xl|xxl}. The wrapper switches between scroll and natural layout at the named breakpoint.
Customization
Seventeen variables retune .table without touching component CSS. Override on .table itself, on a parent scope, or on :root, and the cascade scopes the change.
Geometry
Variable Default Use
--table-cell-padding-y
calc(0.75rem * var(--st-density))
Vertical cell padding
--table-cell-padding-x
calc(0.75rem * var(--st-density))
Horizontal cell padding
--table-cell-padding-sm
calc(0.25rem * var(--st-density))
Cell padding under .table--sm
--table-edge-padding
calc(1.25rem * var(--st-density))
First and last column inset, lines up with the card body gutter
--table-group-divider-width
2px
Rule above .table__body--divider
Head
Variable Default Use
--table-head-font-size
0.75rem
Header label size
--table-head-font-weight
500
Header label weight (down from browser-default 700)
--table-head-color
var(--st-muted-foreground)
Header label color
--table-head-bg-alt
var(--st-surface-2)
Fill applied by .table__head--alt
Surface
Variable Default Use
--table-color
var(--st-foreground)
Body text color
--table-bg
transparent
Cell background (rest layer)
--table-border-color
var(--st-border)
Row and cell border color
Row states
Variable Default Use
--table-striped-bg
color-mix(in oklch, var(--st-foreground) 4%, transparent)
Odd row / even column tint
--table-striped-color
var(--st-foreground)
Striped row text color
--table-hover-bg
var(--st-accent)
Row hover background under .table--hover
--table-hover-color
var(--st-accent-foreground)
Row hover text color
--table-active-bg
var(--st-highlight)
Selected row background for data-state="active"
--table-active-color
var(--st-highlight-foreground)
Selected row text color
The cell paint chain reads --table-bg-state (hover, active) over --table-bg-type (variant, striped) over --table-bg. Variants set -type and runtime states set -state, so hover lights a striped row without erasing the stripe underneath.
73k</td><td>
31k</td>
</tr>
</tbody>
</table>Hoverable rows
Add .table--hover to highlight the row under the cursor. Composes with .table--striped.
| Endpoint | Method | Calls (24h) | Error rate |
|---|---|---|---|
/v1/charges | POST | 48,210 | 0.12% |
/v1/customers | GET | 31,089 | 0.04% |
/v1/invoices | POST | 9,402 | 0.31% |
/v1/webhooks | POST | 2,118 | 1.84% |
<table class="table table--hover">
<thead>
<tr>
<th scope="col">Endpoint</th>
<th scope="col">Method</th>
<th scope="col">Calls (24h)</th>
<th scope="col" class="text-end">Error rate</th>
</tr>
</thead>
<tbody>
<tr><td><code>/v1/charges</code></td><td>POST</td><td>48,210</td><td class="text-end">0.12%</td></tr>
<tr><td><code>/v1/customers</code></td><td>GET</td><td>31,089</td><td class="text-end">0.04%</td></tr>
<tr><td><code>/v1/invoices</code></td><td>POST</td><td>9,402</td><td class="text-end">0.31%</td></tr>
<tr><td><code>/v1/webhooks</code></td><td>POST</td><td>2,118</td><td class="text-end">1.84%</td></tr>
</tbody>
</table>Active row
Flag the persistent selection with data-state="active" on the <tr>. Runs through the highlight tier, same as sidebar and dropdown selection.
| Branch | Last commit | Author | Behind / Ahead |
|---|---|---|---|
| main | Bump axios to 1.7.4 | Mateo Reyes | 0 / 0 |
| feat/billing-v2 | Add proration preview | Maya Singh | 0 / 14 |
| fix/race-on-replay | Drain queue before close | Theo Wright | 3 / 2 |
| chore/upgrade-node-20 | Pin engines field | Sara Lin | 6 / 1 |
<table class="table">
<thead>
<tr>
<th scope="col">Branch</th>
<th scope="col">Last commit</th>
<th scope="col">Author</th>
<th scope="col" class="text-end">Behind / Ahead</th>
</tr>
</thead>
<tbody>
<tr><td>main</td><td>Bump axios to 1.7.4</td><td>Mateo Reyes</td><td class="text-end">0 / 0</td></tr>
<tr data-state="active"><td>feat/billing-v2</td><td>Add proration preview</td><td>Maya Singh</td><td class="text-end">0 / 14</td></tr>
<tr><td>fix/race-on-replay</td><td>Drain queue before close</td><td>Theo Wright</td><td class="text-end">3 / 2</td></tr>
<tr><td>chore/upgrade-node-20</td><td>Pin engines field</td><td>Sara Lin</td><td class="text-end">6 / 1</td></tr>
</tbody>
</table>Bordered
.table--bordered draws a border on every side of every cell.
| Cluster | Nodes | CPU | Memory |
|---|---|---|---|
| prod-edge | 24 | 62% | 71% |
| prod-core | 48 | 54% | 68% |
| staging | 6 | 18% | 22% |
<table class="table table--bordered">
<thead>
<tr>
<th scope="col">Cluster</th>
<th scope="col">Nodes</th>
<th scope="col">CPU</th>
<th scope="col">Memory</th>
</tr>
</thead>
<tbody>
<tr><td>prod-edge</td><td>24</td><td>62%</td><td>71%</td></tr>
<tr><td>prod-core</td><td>48</td><td>54%</td><td>68%</td></tr>
<tr><td>staging</td><td>6</td><td>18%</td><td>22%</td></tr>
</tbody>
</table>Borderless
.table--borderless strips every row and cell border for a soft list look.
| Project | Owner | Open issues |
|---|---|---|
| billing-service | Maya Singh | 12 |
| auth-gateway | Mateo Reyes | 4 |
| events-pipeline | Sara Lin | 7 |
| web-dashboard | Theo Wright | 23 |
<table class="table table--borderless">
<thead>
<tr>
<th scope="col">Project</th>
<th scope="col">Owner</th>
<th scope="col" class="text-end">Open issues</th>
</tr>
</thead>
<tbody>
<tr><td>billing-service</td><td>Maya Singh</td><td class="text-end">12</td></tr>
<tr><td>auth-gateway</td><td>Mateo Reyes</td><td class="text-end">4</td></tr>
<tr><td>events-pipeline</td><td>Sara Lin</td><td class="text-end">7</td></tr>
<tr><td>web-dashboard</td><td>Theo Wright</td><td class="text-end">23</td></tr>
</tbody>
</table>Small
.table--sm shrinks inner cell padding for denser rows. Edge padding stays so the flush-in-card alignment holds.
| Key | Value | Last edited |
|---|---|---|
SMTP_HOST | smtp.postmark.io | Maya · 3 days ago |
SMTP_PORT | 587 | Maya · 3 days ago |
S3_BUCKET | stisla-assets-prod | Theo · last week |
FEATURE_BILLING_V2 | true | Mateo · 1 hour ago |
RATE_LIMIT_RPM | 600 | Sara · yesterday |
<table class="table table--sm">
<thead>
<tr>
<th scope="col">Key</th>
<th scope="col">Value</th>
<th scope="col">Last edited</th>
</tr>
</thead>
<tbody>
<tr><td><code>SMTP_HOST</code></td><td>smtp.postmark.io</td><td>Maya · 3 days ago</td></tr>
<tr><td><code>SMTP_PORT</code></td><td>587</td><td>Maya · 3 days ago</td></tr>
<tr><td><code>S3_BUCKET</code></td><td>stisla-assets-prod</td><td>Theo · last week</td></tr>
<tr><td><code>FEATURE_BILLING_V2</code></td><td>true</td><td>Mateo · 1 hour ago</td></tr>
<tr><td><code>RATE_LIMIT_RPM</code></td><td>600</td><td>Sara · yesterday</td></tr>
</tbody>
</table>Vertical alignment
Cell content sits at the top by default. Add .table--align-middle when rows mix multi-line text with shorter values.
| Document | Summary | Pages |
|---|---|---|
| Onboarding handbook | Day-1 setup, IT accounts, payroll forms, and the company-wide reading list new hires work through in their first week. | 42 |
| Security policy | Device requirements, password rules, incident reporting, and the quarterly review schedule. | 18 |
| Engineering RFCs | Active proposals up for review this cycle. Each row links to the long-form doc. | 7 |
<table class="table table--align-middle">
<thead>
<tr>
<th scope="col">Document</th>
<th scope="col">Summary</th>
<th scope="col" class="text-end">Pages</th>
</tr>
</thead>
<tbody>
<tr>
<td>Onboarding handbook</td>
<td>Day-1 setup, IT accounts, payroll forms, and the company-wide reading list new hires work through in their first week.</td>
<td class="text-end">42</td>
</tr>
<tr>
<td>Security policy</td>
<td>Device requirements, password rules, incident reporting, and the quarterly review schedule.</td>
<td class="text-end">18</td>
</tr>
<tr>
<td>Engineering RFCs</td>
<td>Active proposals up for review this cycle. Each row links to the long-form doc.</td>
<td class="text-end">7</td>
</tr>
</tbody>
</table>Caption
A <caption> sits below the table and reads like a footnote. Promote it above the table with .caption-top.
| Job | Schedule | Last run |
|---|---|---|
| weekly-invoice-rollup | Mon 02:00 UTC | 2 days ago · succeeded |
| nightly-vacuum | Daily 03:30 UTC | 6 hours ago · succeeded |
| hourly-replay | Hourly :15 | 27 min ago · failed |
<table class="table">
<caption>Last refreshed at 09:42 UTC</caption>
<thead>
<tr>
<th scope="col">Job</th>
<th scope="col">Schedule</th>
<th scope="col">Last run</th>
</tr>
</thead>
<tbody>
<tr><td>weekly-invoice-rollup</td><td>Mon 02:00 UTC</td><td>2 days ago · succeeded</td></tr>
<tr><td>nightly-vacuum</td><td>Daily 03:30 UTC</td><td>6 hours ago · succeeded</td></tr>
<tr><td>hourly-replay</td><td>Hourly :15</td><td>27 min ago · failed</td></tr>
</tbody>
</table>Header alt
Apply .table__head--alt on the <thead> to opt the header row onto the alt surface. Same two-tone language as .card__header--alt.
| Customer | Plan | MRR | Renews |
|---|---|---|---|
| Acme Corp | Business | ,490 | Aug 12 |
| Riverway Ltd | Team | $580 | Sep 04 |
| Northwind | Enterprise | $8,200 | Oct 22 |
| Globex | Team | $580 | Nov 09 |
<table class="table">
<thead class="table__head--alt">
<tr>
<th scope="col">Customer</th>
<th scope="col">Plan</th>
<th scope="col">MRR</th>
<th scope="col" class="text-end">Renews</th>
</tr>
</thead>
<tbody>
<tr><td>Acme Corp</td><td>Business</td><td>,490</td><td class="text-end">Aug 12</td></tr>
<tr><td>Riverway Ltd</td><td>Team</td><td>$580</td><td class="text-end">Sep 04</td></tr>
<tr><td>Northwind</td><td>Enterprise</td><td>$8,200</td><td class="text-end">Oct 22</td></tr>
<tr><td>Globex</td><td>Team</td><td>$580</td><td class="text-end">Nov 09</td></tr>
</tbody>
</table>
With status badges
Drop a .badge into a cell to flag state. Soft .badge--soft variants read cleaner inside a dense row than solid fills.
| Invoice | Client | Amount | Due | Status | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
INV-1042 |
Acme Corp | $4,800.00 | Jun 15 | Sent | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
INV-1041 |
Riverway Ltd | ,250.00 |
Jun 10 | Paid | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
INV-1040 |
Northwind | $9,310.00 | May 28 | Overdue | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
INV-1039 |
Globex |
TableA flat data grid for rows of structured records. BasicWrap your data in
Row variantsAdd
Striped rowsAdd
Striped columnsUse
Hoverable rowsAdd
Active rowFlag the persistent selection with
Bordered
Borderless
Small
Vertical alignmentCell content sits at the top by default. Add
CaptionA
Header altApply
With status badgesDrop a
With user avatarsStack an
With row actionsTrailing buttons go in the last cell with
Selectable rowsPut a
Pair with a header that surfaces the bulk count and actions once a row is checked. The count chip uses
2 of 4 selected
Inside a cardDrop the table straight into a
Deployments
Full dashboard tableComposes everything above. Alt-surface header, avatars in the first cell, badges for status, row actions trailing.
Team members
5 of 15 seats
Group dividerAdd
ResponsiveWrap the table in
For breakpoint-scoped scrolling, swap to CustomizationSeventeen variables retune Geometry
Head
Surface
Row states
The cell paint chain reads |
Jun 22 | Draft | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
INV-1038 |
Initech | $680.00 | Jun 30 | Scheduled |
<table class="table table--hover table--align-middle">
<thead class="table__head--alt">
<tr>
<th scope="col">Invoice</th>
<th scope="col">Client</th>
<th scope="col">Amount</th>
<th scope="col">Due</th>
<th scope="col">Status</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>INV-1042</code></td>
<td>Acme Corp</td>
<td>$4,800.00</td>
<td>Jun 15</td>
<td><span class="badge badge--soft badge--info"><i data-lucide="send"></i> Sent</span></td>
</tr>
<tr>
<td><code>INV-1041</code></td>
<td>Riverway Ltd</td>
<td>,250.00</td>
<td>Jun 10</td>
<td><span class="badge badge--soft badge--success"><i data-lucide="check"></i> Paid</span></td>
</tr>
<tr>
<td><code>INV-1040</code></td>
<td>Northwind</td>
<td>$9,310.00</td>
<td>May 28</td>
<td><span class="badge badge--soft badge--danger"><i data-lucide="alert-triangle"></i> Overdue</span></td>
</tr>
<tr>
<td><code>INV-1039</code></td>
<td>Globex</td>
<td>
Table
A flat data grid for rows of structured records.
Basic
Wrap your data in .table. The first and last column cells line up with the card body gutter so a table drops into a .card flush.
Plan
Seats
Storage
Price
Starter
3
10 GB
$0/mo
Team
15
100 GB
$29/mo
Business
50
1 TB
$99/mo
Enterprise
Unlimited
Custom
Talk to sales
<table class="table">
<thead>
<tr>
<th scope="col">Plan</th>
<th scope="col">Seats</th>
<th scope="col">Storage</th>
<th scope="col" class="text-end">Price</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">Starter</th>
<td>3</td>
<td>10 GB</td>
<td class="text-end">$0/mo</td>
</tr>
<tr>
<th scope="row">Team</th>
<td>15</td>
<td>100 GB</td>
<td class="text-end">$29/mo</td>
</tr>
<tr>
<th scope="row">Business</th>
<td>50</td>
<td>1 TB</td>
<td class="text-end">$99/mo</td>
</tr>
<tr>
<th scope="row">Enterprise</th>
<td>Unlimited</td>
<td>Custom</td>
<td class="text-end">Talk to sales</td>
</tr>
</tbody>
</table>
Row variants
Add .table__row--{intent} to a <tr> to call out a row that needs attention. Below is a job queue where one job is mid-retry and another has failed. Available intents are primary, success, info, warning, danger, plus neutral for a quiet emphasis.
Job
Schedule
Last run
Duration
nightly-backup
Daily 02:00 UTC
4 hours ago
3m 12s
events-rollup
Hourly :00
Retrying (2 of 5)
—
invoice-export
Mon 06:00 UTC
Yesterday
1m 04s
webhook-replay
Every 15 min
Failed · 12 min ago
—
metrics-aggregate
Every 5 min
2 min ago
7s
<table class="table table--align-middle">
<thead>
<tr>
<th scope="col">Job</th>
<th scope="col">Schedule</th>
<th scope="col">Last run</th>
<th scope="col" class="text-end">Duration</th>
</tr>
</thead>
<tbody>
<tr>
<td>nightly-backup</td>
<td>Daily 02:00 UTC</td>
<td>4 hours ago</td>
<td class="text-end">3m 12s</td>
</tr>
<tr class="table__row--warning">
<td>events-rollup</td>
<td>Hourly :00</td>
<td>Retrying (2 of 5)</td>
<td class="text-end">—</td>
</tr>
<tr>
<td>invoice-export</td>
<td>Mon 06:00 UTC</td>
<td>Yesterday</td>
<td class="text-end">1m 04s</td>
</tr>
<tr class="table__row--danger">
<td>webhook-replay</td>
<td>Every 15 min</td>
<td>Failed · 12 min ago</td>
<td class="text-end">—</td>
</tr>
<tr>
<td>metrics-aggregate</td>
<td>Every 5 min</td>
<td>2 min ago</td>
<td class="text-end">7s</td>
</tr>
</tbody>
</table>
Striped rows
Add .table--striped to zebra-stripe every other row in the body.
Region
Latency p50
Latency p99
Requests / min
us-east-1 42 ms 180 ms 12,840
us-west-2 58 ms 220 ms 9,210
eu-west-1 61 ms 240 ms 7,455
ap-southeast-1 74 ms 310 ms 3,902
sa-east-1 110 ms 420 ms 1,288
<table class="table table--striped">
<thead>
<tr>
<th scope="col">Region</th>
<th scope="col">Latency p50</th>
<th scope="col">Latency p99</th>
<th scope="col" class="text-end">Requests / min</th>
</tr>
</thead>
<tbody>
<tr><td>us-east-1</td><td>42 ms</td><td>180 ms</td><td class="text-end">12,840</td></tr>
<tr><td>us-west-2</td><td>58 ms</td><td>220 ms</td><td class="text-end">9,210</td></tr>
<tr><td>eu-west-1</td><td>61 ms</td><td>240 ms</td><td class="text-end">7,455</td></tr>
<tr><td>ap-southeast-1</td><td>74 ms</td><td>310 ms</td><td class="text-end">3,902</td></tr>
<tr><td>sa-east-1</td><td>110 ms</td><td>420 ms</td><td class="text-end">1,288</td></tr>
</tbody>
</table>
Striped columns
Use .table--striped-cols when the table reads column-first.
Quarter
Q1
Q2
Q3
Q4
New accounts
418 502 611 730
Churned
62 71 54 48
Net new ARR
$182k $214k $273k $331k
<table class="table table--striped-cols">
<thead>
<tr>
<th scope="col">Quarter</th>
<th scope="col">Q1</th>
<th scope="col">Q2</th>
<th scope="col">Q3</th>
<th scope="col">Q4</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">New accounts</th>
<td>418</td><td>502</td><td>611</td><td>730</td>
</tr>
<tr>
<th scope="row">Churned</th>
<td>62</td><td>71</td><td>54</td><td>48</td>
</tr>
<tr>
<th scope="row">Net new ARR</th>
<td>$182k</td><td>$214k</td><td>$273k</td><td>$331k</td>
</tr>
</tbody>
</table>
Hoverable rows
Add .table--hover to highlight the row under the cursor. Composes with .table--striped.
Endpoint
Method
Calls (24h)
Error rate
/v1/chargesPOST 48,210 0.12%
/v1/customersGET 31,089 0.04%
/v1/invoicesPOST 9,402 0.31%
/v1/webhooksPOST 2,118 1.84%
<table class="table table--hover">
<thead>
<tr>
<th scope="col">Endpoint</th>
<th scope="col">Method</th>
<th scope="col">Calls (24h)</th>
<th scope="col" class="text-end">Error rate</th>
</tr>
</thead>
<tbody>
<tr><td><code>/v1/charges</code></td><td>POST</td><td>48,210</td><td class="text-end">0.12%</td></tr>
<tr><td><code>/v1/customers</code></td><td>GET</td><td>31,089</td><td class="text-end">0.04%</td></tr>
<tr><td><code>/v1/invoices</code></td><td>POST</td><td>9,402</td><td class="text-end">0.31%</td></tr>
<tr><td><code>/v1/webhooks</code></td><td>POST</td><td>2,118</td><td class="text-end">1.84%</td></tr>
</tbody>
</table>
Active row
Flag the persistent selection with data-state="active" on the <tr>. Runs through the highlight tier, same as sidebar and dropdown selection.
Branch
Last commit
Author
Behind / Ahead
main Bump axios to 1.7.4 Mateo Reyes 0 / 0
feat/billing-v2 Add proration preview Maya Singh 0 / 14
fix/race-on-replay Drain queue before close Theo Wright 3 / 2
chore/upgrade-node-20 Pin engines field Sara Lin 6 / 1
<table class="table">
<thead>
<tr>
<th scope="col">Branch</th>
<th scope="col">Last commit</th>
<th scope="col">Author</th>
<th scope="col" class="text-end">Behind / Ahead</th>
</tr>
</thead>
<tbody>
<tr><td>main</td><td>Bump axios to 1.7.4</td><td>Mateo Reyes</td><td class="text-end">0 / 0</td></tr>
<tr data-state="active"><td>feat/billing-v2</td><td>Add proration preview</td><td>Maya Singh</td><td class="text-end">0 / 14</td></tr>
<tr><td>fix/race-on-replay</td><td>Drain queue before close</td><td>Theo Wright</td><td class="text-end">3 / 2</td></tr>
<tr><td>chore/upgrade-node-20</td><td>Pin engines field</td><td>Sara Lin</td><td class="text-end">6 / 1</td></tr>
</tbody>
</table>
Bordered
.table--bordered draws a border on every side of every cell.
Cluster
Nodes
CPU
Memory
prod-edge 24 62% 71%
prod-core 48 54% 68%
staging 6 18% 22%
<table class="table table--bordered">
<thead>
<tr>
<th scope="col">Cluster</th>
<th scope="col">Nodes</th>
<th scope="col">CPU</th>
<th scope="col">Memory</th>
</tr>
</thead>
<tbody>
<tr><td>prod-edge</td><td>24</td><td>62%</td><td>71%</td></tr>
<tr><td>prod-core</td><td>48</td><td>54%</td><td>68%</td></tr>
<tr><td>staging</td><td>6</td><td>18%</td><td>22%</td></tr>
</tbody>
</table>
Borderless
.table--borderless strips every row and cell border for a soft list look.
Project
Owner
Open issues
billing-service Maya Singh 12
auth-gateway Mateo Reyes 4
events-pipeline Sara Lin 7
web-dashboard Theo Wright 23
<table class="table table--borderless">
<thead>
<tr>
<th scope="col">Project</th>
<th scope="col">Owner</th>
<th scope="col" class="text-end">Open issues</th>
</tr>
</thead>
<tbody>
<tr><td>billing-service</td><td>Maya Singh</td><td class="text-end">12</td></tr>
<tr><td>auth-gateway</td><td>Mateo Reyes</td><td class="text-end">4</td></tr>
<tr><td>events-pipeline</td><td>Sara Lin</td><td class="text-end">7</td></tr>
<tr><td>web-dashboard</td><td>Theo Wright</td><td class="text-end">23</td></tr>
</tbody>
</table>
Small
.table--sm shrinks inner cell padding for denser rows. Edge padding stays so the flush-in-card alignment holds.
Key
Value
Last edited
SMTP_HOSTsmtp.postmark.io Maya · 3 days ago
SMTP_PORT587 Maya · 3 days ago
S3_BUCKETstisla-assets-prod Theo · last week
FEATURE_BILLING_V2true Mateo · 1 hour ago
RATE_LIMIT_RPM600 Sara · yesterday
<table class="table table--sm">
<thead>
<tr>
<th scope="col">Key</th>
<th scope="col">Value</th>
<th scope="col">Last edited</th>
</tr>
</thead>
<tbody>
<tr><td><code>SMTP_HOST</code></td><td>smtp.postmark.io</td><td>Maya · 3 days ago</td></tr>
<tr><td><code>SMTP_PORT</code></td><td>587</td><td>Maya · 3 days ago</td></tr>
<tr><td><code>S3_BUCKET</code></td><td>stisla-assets-prod</td><td>Theo · last week</td></tr>
<tr><td><code>FEATURE_BILLING_V2</code></td><td>true</td><td>Mateo · 1 hour ago</td></tr>
<tr><td><code>RATE_LIMIT_RPM</code></td><td>600</td><td>Sara · yesterday</td></tr>
</tbody>
</table>
Vertical alignment
Cell content sits at the top by default. Add .table--align-middle when rows mix multi-line text with shorter values.
Document
Summary
Pages
Onboarding handbook
Day-1 setup, IT accounts, payroll forms, and the company-wide reading list new hires work through in their first week.
42
Security policy
Device requirements, password rules, incident reporting, and the quarterly review schedule.
18
Engineering RFCs
Active proposals up for review this cycle. Each row links to the long-form doc.
7
<table class="table table--align-middle">
<thead>
<tr>
<th scope="col">Document</th>
<th scope="col">Summary</th>
<th scope="col" class="text-end">Pages</th>
</tr>
</thead>
<tbody>
<tr>
<td>Onboarding handbook</td>
<td>Day-1 setup, IT accounts, payroll forms, and the company-wide reading list new hires work through in their first week.</td>
<td class="text-end">42</td>
</tr>
<tr>
<td>Security policy</td>
<td>Device requirements, password rules, incident reporting, and the quarterly review schedule.</td>
<td class="text-end">18</td>
</tr>
<tr>
<td>Engineering RFCs</td>
<td>Active proposals up for review this cycle. Each row links to the long-form doc.</td>
<td class="text-end">7</td>
</tr>
</tbody>
</table>
Caption
A <caption> sits below the table and reads like a footnote. Promote it above the table with .caption-top.
Last refreshed at 09:42 UTC
Job
Schedule
Last run
weekly-invoice-rollup Mon 02:00 UTC 2 days ago · succeeded
nightly-vacuum Daily 03:30 UTC 6 hours ago · succeeded
hourly-replay Hourly :15 27 min ago · failed
<table class="table">
<caption>Last refreshed at 09:42 UTC</caption>
<thead>
<tr>
<th scope="col">Job</th>
<th scope="col">Schedule</th>
<th scope="col">Last run</th>
</tr>
</thead>
<tbody>
<tr><td>weekly-invoice-rollup</td><td>Mon 02:00 UTC</td><td>2 days ago · succeeded</td></tr>
<tr><td>nightly-vacuum</td><td>Daily 03:30 UTC</td><td>6 hours ago · succeeded</td></tr>
<tr><td>hourly-replay</td><td>Hourly :15</td><td>27 min ago · failed</td></tr>
</tbody>
</table>
Header alt
Apply .table__head--alt on the <thead> to opt the header row onto the alt surface. Same two-tone language as .card__header--alt.
Customer
Plan
MRR
Renews
Acme Corp Business $1,490 Aug 12
Riverway Ltd Team $580 Sep 04
Northwind Enterprise $8,200 Oct 22
Globex Team $580 Nov 09
<table class="table">
<thead class="table__head--alt">
<tr>
<th scope="col">Customer</th>
<th scope="col">Plan</th>
<th scope="col">MRR</th>
<th scope="col" class="text-end">Renews</th>
</tr>
</thead>
<tbody>
<tr><td>Acme Corp</td><td>Business</td><td>$1,490</td><td class="text-end">Aug 12</td></tr>
<tr><td>Riverway Ltd</td><td>Team</td><td>$580</td><td class="text-end">Sep 04</td></tr>
<tr><td>Northwind</td><td>Enterprise</td><td>$8,200</td><td class="text-end">Oct 22</td></tr>
<tr><td>Globex</td><td>Team</td><td>$580</td><td class="text-end">Nov 09</td></tr>
</tbody>
</table>
With status badges
Drop a .badge into a cell to flag state. Soft .badge--soft variants read cleaner inside a dense row than solid fills.
Invoice
Client
Amount
Due
Status
INV-1042
Acme Corp
$4,800.00
Jun 15
Sent
INV-1041
Riverway Ltd
$1,250.00
Jun 10
Paid
INV-1040
Northwind
$9,310.00
May 28
Overdue
INV-1039
Globex
$2,140.00
Jun 22
Draft
INV-1038
Initech
$680.00
Jun 30
Scheduled
<table class="table table--hover table--align-middle">
<thead class="table__head--alt">
<tr>
<th scope="col">Invoice</th>
<th scope="col">Client</th>
<th scope="col">Amount</th>
<th scope="col">Due</th>
<th scope="col">Status</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>INV-1042</code></td>
<td>Acme Corp</td>
<td>$4,800.00</td>
<td>Jun 15</td>
<td><span class="badge badge--soft badge--info"><i data-lucide="send"></i> Sent</span></td>
</tr>
<tr>
<td><code>INV-1041</code></td>
<td>Riverway Ltd</td>
<td>$1,250.00</td>
<td>Jun 10</td>
<td><span class="badge badge--soft badge--success"><i data-lucide="check"></i> Paid</span></td>
</tr>
<tr>
<td><code>INV-1040</code></td>
<td>Northwind</td>
<td>$9,310.00</td>
<td>May 28</td>
<td><span class="badge badge--soft badge--danger"><i data-lucide="alert-triangle"></i> Overdue</span></td>
</tr>
<tr>
<td><code>INV-1039</code></td>
<td>Globex</td>
<td>$2,140.00</td>
<td>Jun 22</td>
<td><span class="badge badge--soft badge--warning"><i data-lucide="clock"></i> Draft</span></td>
</tr>
<tr>
<td><code>INV-1038</code></td>
<td>Initech</td>
<td>$680.00</td>
<td>Jun 30</td>
<td><span class="badge badge--soft">Scheduled</span></td>
</tr>
</tbody>
</table>
With user avatars
Stack an <img> and a <div> in a flex cell to pair an avatar with a name and a secondary line. The image keeps its natural shape, so set border-radius: 50% and a width attribute.
Member
Role
Joined
Status
Maya Singh
maya@acme.co
Admin
Jan 2024
Active
Mateo Reyes
mateo@acme.co
Editor
Mar 2024
Active
Sara Lin
sara@acme.co
Viewer
May 2024
Invite pending
Theo Wright
theo@acme.co
Editor
Jun 2023
Suspended
<table class="table table--align-middle">
<thead class="table__head--alt">
<tr>
<th scope="col">Member</th>
<th scope="col">Role</th>
<th scope="col">Joined</th>
<th scope="col">Status</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<div class="d-flex align-items-center gap-2">
<img src="https://images.unsplash.com/photo-1494790108377-be9c29b29330?auto=format&fit=facearea&facepad=2.5&w=120&h=120&q=80" alt="" width="32" height="32" style="border-radius: 50%;" />
<div>
<div>Maya Singh</div>
<div class="text-muted-foreground">maya@acme.co</div>
</div>
</div>
</td>
<td>Admin</td>
<td>Jan 2024</td>
<td><span class="badge badge--soft badge--success">Active</span></td>
</tr>
<tr>
<td>
<div class="d-flex align-items-center gap-2">
<img src="https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?auto=format&fit=facearea&facepad=2.5&w=120&h=120&q=80" alt="" width="32" height="32" style="border-radius: 50%;" />
<div>
<div>Mateo Reyes</div>
<div class="text-muted-foreground">mateo@acme.co</div>
</div>
</div>
</td>
<td>Editor</td>
<td>Mar 2024</td>
<td><span class="badge badge--soft badge--success">Active</span></td>
</tr>
<tr>
<td>
<div class="d-flex align-items-center gap-2">
<img src="https://images.unsplash.com/photo-1438761681033-6461ffad8d80?auto=format&fit=facearea&facepad=2.5&w=120&h=120&q=80" alt="" width="32" height="32" style="border-radius: 50%;" />
<div>
<div>Sara Lin</div>
<div class="text-muted-foreground">sara@acme.co</div>
</div>
</div>
</td>
<td>Viewer</td>
<td>May 2024</td>
<td><span class="badge badge--soft badge--warning">Invite pending</span></td>
</tr>
<tr>
<td>
<div class="d-flex align-items-center gap-2">
<img src="https://images.unsplash.com/photo-1500648767791-00dcc994a43e?auto=format&fit=facearea&facepad=2.5&w=120&h=120&q=80" alt="" width="32" height="32" style="border-radius: 50%;" />
<div>
<div>Theo Wright</div>
<div class="text-muted-foreground">theo@acme.co</div>
</div>
</div>
</td>
<td>Editor</td>
<td>Jun 2023</td>
<td><span class="badge badge--soft badge--danger">Suspended</span></td>
</tr>
</tbody>
</table>
With row actions
Trailing buttons go in the last cell with .text-end. Ghost icon buttons sit quiet until the row is hovered.
API key
Scope
Created
Last used
Actions
sk_live_•••••8a1c
Live
Apr 12, 2024
2 mins ago
sk_test_•••••3f02
Test
Jan 03, 2024
1 hour ago
sk_live_•••••c4e9
Live
Nov 28, 2023
3 days ago
<table class="table table--hover table--align-middle">
<thead class="table__head--alt">
<tr>
<th scope="col">API key</th>
<th scope="col">Scope</th>
<th scope="col">Created</th>
<th scope="col">Last used</th>
<th scope="col" class="text-end">Actions</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>sk_live_•••••8a1c</code></td>
<td><span class="badge badge--soft badge--primary">Live</span></td>
<td>Apr 12, 2024</td>
<td>2 mins ago</td>
<td class="text-end">
<div class="d-inline-flex gap-1">
<button type="button" class="btn btn--sm btn--ghost btn--neutral btn--icon-only" aria-label="Copy">
<i data-lucide="copy"></i>
</button>
<button type="button" class="btn btn--sm btn--ghost btn--neutral btn--icon-only" aria-label="Revoke">
<i data-lucide="trash-2"></i>
</button>
</div>
</td>
</tr>
<tr>
<td><code>sk_test_•••••3f02</code></td>
<td><span class="badge badge--soft">Test</span></td>
<td>Jan 03, 2024</td>
<td>1 hour ago</td>
<td class="text-end">
<div class="d-inline-flex gap-1">
<button type="button" class="btn btn--sm btn--ghost btn--neutral btn--icon-only" aria-label="Copy">
<i data-lucide="copy"></i>
</button>
<button type="button" class="btn btn--sm btn--ghost btn--neutral btn--icon-only" aria-label="Revoke">
<i data-lucide="trash-2"></i>
</button>
</div>
</td>
</tr>
<tr>
<td><code>sk_live_•••••c4e9</code></td>
<td><span class="badge badge--soft badge--primary">Live</span></td>
<td>Nov 28, 2023</td>
<td>3 days ago</td>
<td class="text-end">
<div class="d-inline-flex gap-1">
<button type="button" class="btn btn--sm btn--ghost btn--neutral btn--icon-only" aria-label="Copy">
<i data-lucide="copy"></i>
</button>
<button type="button" class="btn btn--sm btn--ghost btn--neutral btn--icon-only" aria-label="Revoke">
<i data-lucide="trash-2"></i>
</button>
</div>
</td>
</tr>
</tbody>
</table>
Selectable rows
Put a .checkbox in the first column to let people pick rows. The header checkbox is the bulk toggle; flip data-state="active" on the row to mark it selected. The data-demo-select-rows hook on the table wires the live behavior.
Email
Source
Subscribed
Status
maya@acme.co
Landing page
Jun 02
Confirmed
mateo@acme.co
Referral · Sara
Jun 01
Confirmed
sara@acme.co
Webinar · Q2 launch
May 28
Pending
theo@acme.co
Import · Mailchimp
May 22
Unsubscribed
alex@acme.co
Landing page
May 19
Confirmed
<table class="table table--hover table--align-middle" data-demo-select-rows>
<thead class="table__head--alt">
<tr>
<th scope="col" style="width: 1rem;">
<input class="checkbox" type="checkbox" aria-label="Select all subscribers" />
</th>
<th scope="col">Email</th>
<th scope="col">Source</th>
<th scope="col">Subscribed</th>
<th scope="col">Status</th>
</tr>
</thead>
<tbody>
<tr>
<td><input class="checkbox" type="checkbox" aria-label="Select maya@acme.co" /></td>
<td>maya@acme.co</td>
<td>Landing page</td>
<td>Jun 02</td>
<td><span class="badge badge--soft badge--success">Confirmed</span></td>
</tr>
<tr data-state="active">
<td><input class="checkbox" type="checkbox" aria-label="Select mateo@acme.co" checked /></td>
<td>mateo@acme.co</td>
<td>Referral · Sara</td>
<td>Jun 01</td>
<td><span class="badge badge--soft badge--success">Confirmed</span></td>
</tr>
<tr data-state="active">
<td><input class="checkbox" type="checkbox" aria-label="Select sara@acme.co" checked /></td>
<td>sara@acme.co</td>
<td>Webinar · Q2 launch</td>
<td>May 28</td>
<td><span class="badge badge--soft badge--warning">Pending</span></td>
</tr>
<tr>
<td><input class="checkbox" type="checkbox" aria-label="Select theo@acme.co" /></td>
<td>theo@acme.co</td>
<td>Import · Mailchimp</td>
<td>May 22</td>
<td><span class="badge badge--soft">Unsubscribed</span></td>
</tr>
<tr>
<td><input class="checkbox" type="checkbox" aria-label="Select alex@acme.co" /></td>
<td>alex@acme.co</td>
<td>Landing page</td>
<td>May 19</td>
<td><span class="badge badge--soft badge--success">Confirmed</span></td>
</tr>
</tbody>
</table>
Pair with a header that surfaces the bulk count and actions once a row is checked. The count chip uses data-demo-select-count so the script can update it as rows toggle.
2 of 4 selected
From
Subject
Received
Maya Singh
Re: Q3 roadmap draft
9:42 AM
Mateo Reyes
Auth migration status
8:18 AM
Sara Lin
Pipeline retry logic
Yesterday
Theo Wright
Vendor renewal · Jun 30
Yesterday
<div class="card">
<div class="card__header card__header--alt">
<span><strong data-demo-select-count>2</strong> of 4 selected</span>
<div class="d-flex gap-2 ms-auto">
<button type="button" class="btn btn--sm btn--outline btn--neutral">
<i data-lucide="tag"></i>
Add tag
</button>
<button type="button" class="btn btn--sm btn--outline btn--neutral">
<i data-lucide="archive"></i>
Archive
</button>
<button type="button" class="btn btn--sm btn--danger">
<i data-lucide="trash-2"></i>
Delete
</button>
</div>
</div>
<table class="table table--hover table--align-middle mb-0" data-demo-select-rows>
<thead>
<tr>
<th scope="col" style="width: 1rem;">
<input class="checkbox" type="checkbox" aria-label="Select all messages" />
</th>
<th scope="col">From</th>
<th scope="col">Subject</th>
<th scope="col" class="text-end">Received</th>
</tr>
</thead>
<tbody>
<tr data-state="active">
<td><input class="checkbox" type="checkbox" aria-label="Select message from Maya Singh" checked /></td>
<td>Maya Singh</td>
<td>Re: Q3 roadmap draft</td>
<td class="text-end">9:42 AM</td>
</tr>
<tr data-state="active">
<td><input class="checkbox" type="checkbox" aria-label="Select message from Mateo Reyes" checked /></td>
<td>Mateo Reyes</td>
<td>Auth migration status</td>
<td class="text-end">8:18 AM</td>
</tr>
<tr>
<td><input class="checkbox" type="checkbox" aria-label="Select message from Sara Lin" /></td>
<td>Sara Lin</td>
<td>Pipeline retry logic</td>
<td class="text-end">Yesterday</td>
</tr>
<tr>
<td><input class="checkbox" type="checkbox" aria-label="Select message from Theo Wright" /></td>
<td>Theo Wright</td>
<td>Vendor renewal · Jun 30</td>
<td class="text-end">Yesterday</td>
</tr>
</tbody>
</table>
</div>
Inside a card
Drop the table straight into a .card with margin-bottom: 0 so it sits flush. The edge cells line up with the card header's left padding.
Deployments
Service
Environment
Version
Deployed
Status
api
production
v2.14.0
3 min ago
Healthy
web
production
v3.41.2
12 min ago
Degraded
worker
staging
v0.8.1
1 hour ago
Healthy
api
staging
v2.15.0-rc1
just now
Deploying
<div class="card">
<div class="card__header card__header--alt">
Deployments
<button type="button" class="btn btn--sm btn--primary ms-auto">Deploy</button>
</div>
<table class="table table--hover table--align-middle mb-0">
<thead>
<tr>
<th scope="col">Service</th>
<th scope="col">Environment</th>
<th scope="col">Version</th>
<th scope="col">Deployed</th>
<th scope="col">Status</th>
</tr>
</thead>
<tbody>
<tr>
<td>api</td>
<td><span class="badge badge--soft">production</span></td>
<td><code>v2.14.0</code></td>
<td>3 min ago</td>
<td><span class="badge badge--soft badge--success"><i data-lucide="check-circle-2"></i> Healthy</span></td>
</tr>
<tr>
<td>web</td>
<td><span class="badge badge--soft">production</span></td>
<td><code>v3.41.2</code></td>
<td>12 min ago</td>
<td><span class="badge badge--soft badge--warning"><i data-lucide="triangle-alert"></i> Degraded</span></td>
</tr>
<tr>
<td>worker</td>
<td><span class="badge badge--soft">staging</span></td>
<td><code>v0.8.1</code></td>
<td>1 hour ago</td>
<td><span class="badge badge--soft badge--success"><i data-lucide="check-circle-2"></i> Healthy</span></td>
</tr>
<tr>
<td>api</td>
<td><span class="badge badge--soft">staging</span></td>
<td><code>v2.15.0-rc1</code></td>
<td>just now</td>
<td>
<span class="badge badge--soft badge--info">
<span class="spinner spinner--sm" role="status" aria-hidden="true"></span>
Deploying
</span>
</td>
</tr>
</tbody>
</table>
</div>
Full dashboard table
Composes everything above. Alt-surface header, avatars in the first cell, badges for status, row actions trailing.
Team members
5 of 15 seats
Member
Role
Last active
Status
Actions
Alex Park
alex@acme.co
Owner
just now
Active
Maya Singh
maya@acme.co
Admin
12 min ago
Active
Mateo Reyes
mateo@acme.co
Editor
2 hours ago
Active
Sara Lin
sara@acme.co
Viewer
never
Invite pending
Theo Wright
theo@acme.co
Editor
1 week ago
Suspended
<div class="card">
<div class="card__header card__header--alt">
Team members
<span class="badge badge--soft ms-2">5 of 15 seats</span>
<button type="button" class="btn btn--sm btn--primary ms-auto">
<i data-lucide="user-plus"></i>
Invite
</button>
</div>
<table class="table table--hover table--align-middle mb-0">
<thead>
<tr>
<th scope="col">Member</th>
<th scope="col">Role</th>
<th scope="col">Last active</th>
<th scope="col">Status</th>
<th scope="col" class="text-end">Actions</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<div class="d-flex align-items-center gap-2">
<img src="https://images.unsplash.com/photo-1535713875002-d1d0cf377fde?auto=format&fit=facearea&facepad=2.5&w=120&h=120&q=80" alt="" width="32" height="32" style="border-radius: 50%;" />
<div>
<div>Alex Park</div>
<div class="text-muted-foreground">alex@acme.co</div>
</div>
</div>
</td>
<td><span class="badge badge--soft badge--primary">Owner</span></td>
<td>just now</td>
<td><span class="badge badge--soft badge--success">Active</span></td>
<td class="text-end">
<button type="button" class="btn btn--sm btn--ghost btn--neutral btn--icon-only" aria-label="More">
<i data-lucide="more-horizontal"></i>
</button>
</td>
</tr>
<tr>
<td>
<div class="d-flex align-items-center gap-2">
<img src="https://images.unsplash.com/photo-1494790108377-be9c29b29330?auto=format&fit=facearea&facepad=2.5&w=120&h=120&q=80" alt="" width="32" height="32" style="border-radius: 50%;" />
<div>
<div>Maya Singh</div>
<div class="text-muted-foreground">maya@acme.co</div>
</div>
</div>
</td>
<td><span class="badge badge--soft badge--info">Admin</span></td>
<td>12 min ago</td>
<td><span class="badge badge--soft badge--success">Active</span></td>
<td class="text-end">
<button type="button" class="btn btn--sm btn--ghost btn--neutral btn--icon-only" aria-label="More">
<i data-lucide="more-horizontal"></i>
</button>
</td>
</tr>
<tr>
<td>
<div class="d-flex align-items-center gap-2">
<img src="https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?auto=format&fit=facearea&facepad=2.5&w=120&h=120&q=80" alt="" width="32" height="32" style="border-radius: 50%;" />
<div>
<div>Mateo Reyes</div>
<div class="text-muted-foreground">mateo@acme.co</div>
</div>
</div>
</td>
<td><span class="badge badge--soft">Editor</span></td>
<td>2 hours ago</td>
<td><span class="badge badge--soft badge--success">Active</span></td>
<td class="text-end">
<button type="button" class="btn btn--sm btn--ghost btn--neutral btn--icon-only" aria-label="More">
<i data-lucide="more-horizontal"></i>
</button>
</td>
</tr>
<tr>
<td>
<div class="d-flex align-items-center gap-2">
<img src="https://images.unsplash.com/photo-1438761681033-6461ffad8d80?auto=format&fit=facearea&facepad=2.5&w=120&h=120&q=80" alt="" width="32" height="32" style="border-radius: 50%;" />
<div>
<div>Sara Lin</div>
<div class="text-muted-foreground">sara@acme.co</div>
</div>
</div>
</td>
<td><span class="badge badge--soft">Viewer</span></td>
<td>never</td>
<td><span class="badge badge--soft badge--warning">Invite pending</span></td>
<td class="text-end">
<button type="button" class="btn btn--sm btn--ghost btn--neutral btn--icon-only" aria-label="More">
<i data-lucide="more-horizontal"></i>
</button>
</td>
</tr>
<tr>
<td>
<div class="d-flex align-items-center gap-2">
<img src="https://images.unsplash.com/photo-1500648767791-00dcc994a43e?auto=format&fit=facearea&facepad=2.5&w=120&h=120&q=80" alt="" width="32" height="32" style="border-radius: 50%;" />
<div>
<div>Theo Wright</div>
<div class="text-muted-foreground">theo@acme.co</div>
</div>
</div>
</td>
<td><span class="badge badge--soft">Editor</span></td>
<td>1 week ago</td>
<td><span class="badge badge--soft badge--danger">Suspended</span></td>
<td class="text-end">
<button type="button" class="btn btn--sm btn--ghost btn--neutral btn--icon-only" aria-label="More">
<i data-lucide="more-horizontal"></i>
</button>
</td>
</tr>
</tbody>
</table>
</div>
Group divider
Add .table__body--divider to a <tbody> for a heavier rule above it. Helpful when the head reads as a column label rather than a section break.
Item
Qty
Line total
Pro seat license 15 $435.00
Storage add-on 2 $40.00
Priority support 1 $199.00
<table class="table">
<thead>
<tr>
<th scope="col">Item</th>
<th scope="col">Qty</th>
<th scope="col" class="text-end">Line total</th>
</tr>
</thead>
<tbody class="table__body--divider">
<tr><td>Pro seat license</td><td>15</td><td class="text-end">$435.00</td></tr>
<tr><td>Storage add-on</td><td>2</td><td class="text-end">$40.00</td></tr>
<tr><td>Priority support</td><td>1</td><td class="text-end">$199.00</td></tr>
</tbody>
</table>
Responsive
Wrap the table in .table-responsive when it might overflow narrow viewports. The wrapper scrolls horizontally; the table itself stays unchanged.
Customer
Plan
Seats
MRR
Started
Trial ends
Renews
Owner
Status
Acme Corp Business 48 $1,490
Feb 02, 2024 — Aug 12 Maya Singh
Active
Riverway Ltd Team 14 $580
May 14, 2024 — Sep 04 Mateo Reyes
Active
Northwind Enterprise 112 $8,200
Aug 30, 2022 — Oct 22 Alex Park
Active
Globex Team 9 $580
Jun 01, 2024 Jun 15 — Sara Lin
Trialing
Initech Starter 3 $0
Jun 03, 2024 — — Theo Wright
Free
<div class="table-responsive">
<table class="table">
<thead class="table__head--alt">
<tr>
<th scope="col">Customer</th>
<th scope="col">Plan</th>
<th scope="col">Seats</th>
<th scope="col">MRR</th>
<th scope="col">Started</th>
<th scope="col">Trial ends</th>
<th scope="col">Renews</th>
<th scope="col">Owner</th>
<th scope="col">Status</th>
</tr>
</thead>
<tbody>
<tr>
<td>Acme Corp</td><td>Business</td><td>48</td><td>$1,490</td>
<td>Feb 02, 2024</td><td>—</td><td>Aug 12</td><td>Maya Singh</td>
<td><span class="badge badge--soft badge--success">Active</span></td>
</tr>
<tr>
<td>Riverway Ltd</td><td>Team</td><td>14</td><td>$580</td>
<td>May 14, 2024</td><td>—</td><td>Sep 04</td><td>Mateo Reyes</td>
<td><span class="badge badge--soft badge--success">Active</span></td>
</tr>
<tr>
<td>Northwind</td><td>Enterprise</td><td>112</td><td>$8,200</td>
<td>Aug 30, 2022</td><td>—</td><td>Oct 22</td><td>Alex Park</td>
<td><span class="badge badge--soft badge--success">Active</span></td>
</tr>
<tr>
<td>Globex</td><td>Team</td><td>9</td><td>$580</td>
<td>Jun 01, 2024</td><td>Jun 15</td><td>—</td><td>Sara Lin</td>
<td><span class="badge badge--soft badge--warning">Trialing</span></td>
</tr>
<tr>
<td>Initech</td><td>Starter</td><td>3</td><td>$0</td>
<td>Jun 03, 2024</td><td>—</td><td>—</td><td>Theo Wright</td>
<td><span class="badge badge--soft">Free</span></td>
</tr>
</tbody>
</table>
</div>
For breakpoint-scoped scrolling, swap to .table-responsive-{sm|md|lg|xl|xxl}. The wrapper switches between scroll and natural layout at the named breakpoint.
Customization
Seventeen variables retune .table without touching component CSS. Override on .table itself, on a parent scope, or on :root, and the cascade scopes the change.
Geometry
Variable Default Use
--table-cell-padding-y
calc(0.75rem * var(--st-density))
Vertical cell padding
--table-cell-padding-x
calc(0.75rem * var(--st-density))
Horizontal cell padding
--table-cell-padding-sm
calc(0.25rem * var(--st-density))
Cell padding under .table--sm
--table-edge-padding
calc(1.25rem * var(--st-density))
First and last column inset, lines up with the card body gutter
--table-group-divider-width
2px
Rule above .table__body--divider
Head
Variable Default Use
--table-head-font-size
0.75rem
Header label size
--table-head-font-weight
500
Header label weight (down from browser-default 700)
--table-head-color
var(--st-muted-foreground)
Header label color
--table-head-bg-alt
var(--st-surface-2)
Fill applied by .table__head--alt
Surface
Variable Default Use
--table-color
var(--st-foreground)
Body text color
--table-bg
transparent
Cell background (rest layer)
--table-border-color
var(--st-border)
Row and cell border color
Row states
Variable Default Use
--table-striped-bg
color-mix(in oklch, var(--st-foreground) 4%, transparent)
Odd row / even column tint
--table-striped-color
var(--st-foreground)
Striped row text color
--table-hover-bg
var(--st-accent)
Row hover background under .table--hover
--table-hover-color
var(--st-accent-foreground)
Row hover text color
--table-active-bg
var(--st-highlight)
Selected row background for data-state="active"
--table-active-color
var(--st-highlight-foreground)
Selected row text color
The cell paint chain reads --table-bg-state (hover, active) over --table-bg-type (variant, striped) over --table-bg. Variants set -type and runtime states set -state, so hover lights a striped row without erasing the stripe underneath.
,140.00</td>
<td>Jun 22</td>
<td><span class="badge badge--soft badge--warning"><i data-lucide="clock"></i> Draft</span></td>
</tr>
<tr>
<td><code>INV-1038</code></td>
<td>Initech</td>
<td>$680.00</td>
<td>Jun 30</td>
<td><span class="badge badge--soft">Scheduled</span></td>
</tr>
</tbody>
</table>
With user avatars
Stack an <img> and a <div> in a flex cell to pair an avatar with a name and a secondary line. The image keeps its natural shape, so set border-radius: 50% and a width attribute.
| Member | Role | Joined | Status |
|---|---|---|---|
|
Maya Singh
maya@acme.co
|
Admin | Jan 2024 | Active |
|
Mateo Reyes
mateo@acme.co
|
Editor | Mar 2024 | Active |
|
Sara Lin
sara@acme.co
|
Viewer | May 2024 | Invite pending |
|
Theo Wright
theo@acme.co
|
Editor | Jun 2023 | Suspended |
<table class="table table--align-middle">
<thead class="table__head--alt">
<tr>
<th scope="col">Member</th>
<th scope="col">Role</th>
<th scope="col">Joined</th>
<th scope="col">Status</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<div class="d-flex align-items-center gap-2">
<img src="https://images.unsplash.com/photo-1494790108377-be9c29b29330?auto=format&fit=facearea&facepad=2.5&w=120&h=120&q=80" alt="" width="32" height="32" style="border-radius: 50%;" />
<div>
<div>Maya Singh</div>
<div class="text-muted-foreground">maya@acme.co</div>
</div>
</div>
</td>
<td>Admin</td>
<td>Jan 2024</td>
<td><span class="badge badge--soft badge--success">Active</span></td>
</tr>
<tr>
<td>
<div class="d-flex align-items-center gap-2">
<img src="https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?auto=format&fit=facearea&facepad=2.5&w=120&h=120&q=80" alt="" width="32" height="32" style="border-radius: 50%;" />
<div>
<div>Mateo Reyes</div>
<div class="text-muted-foreground">mateo@acme.co</div>
</div>
</div>
</td>
<td>Editor</td>
<td>Mar 2024</td>
<td><span class="badge badge--soft badge--success">Active</span></td>
</tr>
<tr>
<td>
<div class="d-flex align-items-center gap-2">
<img src="https://images.unsplash.com/photo-1438761681033-6461ffad8d80?auto=format&fit=facearea&facepad=2.5&w=120&h=120&q=80" alt="" width="32" height="32" style="border-radius: 50%;" />
<div>
<div>Sara Lin</div>
<div class="text-muted-foreground">sara@acme.co</div>
</div>
</div>
</td>
<td>Viewer</td>
<td>May 2024</td>
<td><span class="badge badge--soft badge--warning">Invite pending</span></td>
</tr>
<tr>
<td>
<div class="d-flex align-items-center gap-2">
<img src="https://images.unsplash.com/photo-1500648767791-00dcc994a43e?auto=format&fit=facearea&facepad=2.5&w=120&h=120&q=80" alt="" width="32" height="32" style="border-radius: 50%;" />
<div>
<div>Theo Wright</div>
<div class="text-muted-foreground">theo@acme.co</div>
</div>
</div>
</td>
<td>Editor</td>
<td>Jun 2023</td>
<td><span class="badge badge--soft badge--danger">Suspended</span></td>
</tr>
</tbody>
</table>With row actions
Trailing buttons go in the last cell with .text-end. Ghost icon buttons sit quiet until the row is hovered.
| API key | Scope | Created | Last used | Actions |
|---|---|---|---|---|
sk_live_•••••8a1c |
Live | Apr 12, 2024 | 2 mins ago |
|
sk_test_•••••3f02 |
Test | Jan 03, 2024 | 1 hour ago |
|
sk_live_•••••c4e9 |
Live | Nov 28, 2023 | 3 days ago |
|
<table class="table table--hover table--align-middle">
<thead class="table__head--alt">
<tr>
<th scope="col">API key</th>
<th scope="col">Scope</th>
<th scope="col">Created</th>
<th scope="col">Last used</th>
<th scope="col" class="text-end">Actions</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>sk_live_•••••8a1c</code></td>
<td><span class="badge badge--soft badge--primary">Live</span></td>
<td>Apr 12, 2024</td>
<td>2 mins ago</td>
<td class="text-end">
<div class="d-inline-flex gap-1">
<button type="button" class="btn btn--sm btn--ghost btn--neutral btn--icon-only" aria-label="Copy">
<i data-lucide="copy"></i>
</button>
<button type="button" class="btn btn--sm btn--ghost btn--neutral btn--icon-only" aria-label="Revoke">
<i data-lucide="trash-2"></i>
</button>
</div>
</td>
</tr>
<tr>
<td><code>sk_test_•••••3f02</code></td>
<td><span class="badge badge--soft">Test</span></td>
<td>Jan 03, 2024</td>
<td>1 hour ago</td>
<td class="text-end">
<div class="d-inline-flex gap-1">
<button type="button" class="btn btn--sm btn--ghost btn--neutral btn--icon-only" aria-label="Copy">
<i data-lucide="copy"></i>
</button>
<button type="button" class="btn btn--sm btn--ghost btn--neutral btn--icon-only" aria-label="Revoke">
<i data-lucide="trash-2"></i>
</button>
</div>
</td>
</tr>
<tr>
<td><code>sk_live_•••••c4e9</code></td>
<td><span class="badge badge--soft badge--primary">Live</span></td>
<td>Nov 28, 2023</td>
<td>3 days ago</td>
<td class="text-end">
<div class="d-inline-flex gap-1">
<button type="button" class="btn btn--sm btn--ghost btn--neutral btn--icon-only" aria-label="Copy">
<i data-lucide="copy"></i>
</button>
<button type="button" class="btn btn--sm btn--ghost btn--neutral btn--icon-only" aria-label="Revoke">
<i data-lucide="trash-2"></i>
</button>
</div>
</td>
</tr>
</tbody>
</table>Selectable rows
Put a .checkbox in the first column to let people pick rows. The header checkbox is the bulk toggle; flip data-state="active" on the row to mark it selected. The data-demo-select-rows hook on the table wires the live behavior.
| Source | Subscribed | Status | ||
|---|---|---|---|---|
| maya@acme.co | Landing page | Jun 02 | Confirmed | |
| mateo@acme.co | Referral · Sara | Jun 01 | Confirmed | |
| sara@acme.co | Webinar · Q2 launch | May 28 | Pending | |
| theo@acme.co | Import · Mailchimp | May 22 | Unsubscribed | |
| alex@acme.co | Landing page | May 19 | Confirmed |
<table class="table table--hover table--align-middle" data-demo-select-rows>
<thead class="table__head--alt">
<tr>
<th scope="col" style="width: 1rem;">
<input class="checkbox" type="checkbox" aria-label="Select all subscribers" />
</th>
<th scope="col">Email</th>
<th scope="col">Source</th>
<th scope="col">Subscribed</th>
<th scope="col">Status</th>
</tr>
</thead>
<tbody>
<tr>
<td><input class="checkbox" type="checkbox" aria-label="Select maya@acme.co" /></td>
<td>maya@acme.co</td>
<td>Landing page</td>
<td>Jun 02</td>
<td><span class="badge badge--soft badge--success">Confirmed</span></td>
</tr>
<tr data-state="active">
<td><input class="checkbox" type="checkbox" aria-label="Select mateo@acme.co" checked /></td>
<td>mateo@acme.co</td>
<td>Referral · Sara</td>
<td>Jun 01</td>
<td><span class="badge badge--soft badge--success">Confirmed</span></td>
</tr>
<tr data-state="active">
<td><input class="checkbox" type="checkbox" aria-label="Select sara@acme.co" checked /></td>
<td>sara@acme.co</td>
<td>Webinar · Q2 launch</td>
<td>May 28</td>
<td><span class="badge badge--soft badge--warning">Pending</span></td>
</tr>
<tr>
<td><input class="checkbox" type="checkbox" aria-label="Select theo@acme.co" /></td>
<td>theo@acme.co</td>
<td>Import · Mailchimp</td>
<td>May 22</td>
<td><span class="badge badge--soft">Unsubscribed</span></td>
</tr>
<tr>
<td><input class="checkbox" type="checkbox" aria-label="Select alex@acme.co" /></td>
<td>alex@acme.co</td>
<td>Landing page</td>
<td>May 19</td>
<td><span class="badge badge--soft badge--success">Confirmed</span></td>
</tr>
</tbody>
</table>Pair with a header that surfaces the bulk count and actions once a row is checked. The count chip uses data-demo-select-count so the script can update it as rows toggle.
| From | Subject | Received | |
|---|---|---|---|
| Maya Singh | Re: Q3 roadmap draft | 9:42 AM | |
| Mateo Reyes | Auth migration status | 8:18 AM | |
| Sara Lin | Pipeline retry logic | Yesterday | |
| Theo Wright | Vendor renewal · Jun 30 | Yesterday |
<div class="card">
<div class="card__header card__header--alt">
<span><strong data-demo-select-count>2</strong> of 4 selected</span>
<div class="d-flex gap-2 ms-auto">
<button type="button" class="btn btn--sm btn--outline btn--neutral">
<i data-lucide="tag"></i>
Add tag
</button>
<button type="button" class="btn btn--sm btn--outline btn--neutral">
<i data-lucide="archive"></i>
Archive
</button>
<button type="button" class="btn btn--sm btn--danger">
<i data-lucide="trash-2"></i>
Delete
</button>
</div>
</div>
<table class="table table--hover table--align-middle mb-0" data-demo-select-rows>
<thead>
<tr>
<th scope="col" style="width: 1rem;">
<input class="checkbox" type="checkbox" aria-label="Select all messages" />
</th>
<th scope="col">From</th>
<th scope="col">Subject</th>
<th scope="col" class="text-end">Received</th>
</tr>
</thead>
<tbody>
<tr data-state="active">
<td><input class="checkbox" type="checkbox" aria-label="Select message from Maya Singh" checked /></td>
<td>Maya Singh</td>
<td>Re: Q3 roadmap draft</td>
<td class="text-end">9:42 AM</td>
</tr>
<tr data-state="active">
<td><input class="checkbox" type="checkbox" aria-label="Select message from Mateo Reyes" checked /></td>
<td>Mateo Reyes</td>
<td>Auth migration status</td>
<td class="text-end">8:18 AM</td>
</tr>
<tr>
<td><input class="checkbox" type="checkbox" aria-label="Select message from Sara Lin" /></td>
<td>Sara Lin</td>
<td>Pipeline retry logic</td>
<td class="text-end">Yesterday</td>
</tr>
<tr>
<td><input class="checkbox" type="checkbox" aria-label="Select message from Theo Wright" /></td>
<td>Theo Wright</td>
<td>Vendor renewal · Jun 30</td>
<td class="text-end">Yesterday</td>
</tr>
</tbody>
</table>
</div>Inside a card
Drop the table straight into a .card with margin-bottom: 0 so it sits flush. The edge cells line up with the card header's left padding.
| Service | Environment | Version | Deployed | Status |
|---|---|---|---|---|
| api | production | v2.14.0 |
3 min ago | Healthy |
| web | production | v3.41.2 |
12 min ago | Degraded |
| worker | staging | v0.8.1 |
1 hour ago | Healthy |
| api | staging | v2.15.0-rc1 |
just now | Deploying |
<div class="card">
<div class="card__header card__header--alt">
Deployments
<button type="button" class="btn btn--sm btn--primary ms-auto">Deploy</button>
</div>
<table class="table table--hover table--align-middle mb-0">
<thead>
<tr>
<th scope="col">Service</th>
<th scope="col">Environment</th>
<th scope="col">Version</th>
<th scope="col">Deployed</th>
<th scope="col">Status</th>
</tr>
</thead>
<tbody>
<tr>
<td>api</td>
<td><span class="badge badge--soft">production</span></td>
<td><code>v2.14.0</code></td>
<td>3 min ago</td>
<td><span class="badge badge--soft badge--success"><i data-lucide="check-circle-2"></i> Healthy</span></td>
</tr>
<tr>
<td>web</td>
<td><span class="badge badge--soft">production</span></td>
<td><code>v3.41.2</code></td>
<td>12 min ago</td>
<td><span class="badge badge--soft badge--warning"><i data-lucide="triangle-alert"></i> Degraded</span></td>
</tr>
<tr>
<td>worker</td>
<td><span class="badge badge--soft">staging</span></td>
<td><code>v0.8.1</code></td>
<td>1 hour ago</td>
<td><span class="badge badge--soft badge--success"><i data-lucide="check-circle-2"></i> Healthy</span></td>
</tr>
<tr>
<td>api</td>
<td><span class="badge badge--soft">staging</span></td>
<td><code>v2.15.0-rc1</code></td>
<td>just now</td>
<td>
<span class="badge badge--soft badge--info">
<span class="spinner spinner--sm" role="status" aria-hidden="true"></span>
Deploying
</span>
</td>
</tr>
</tbody>
</table>
</div>Full dashboard table
Composes everything above. Alt-surface header, avatars in the first cell, badges for status, row actions trailing.
| Member | Role | Last active | Status | Actions |
|---|---|---|---|---|
|
Alex Park
alex@acme.co
|
Owner | just now | Active | |
|
Maya Singh
maya@acme.co
|
Admin | 12 min ago | Active | |
|
Mateo Reyes
mateo@acme.co
|
Editor | 2 hours ago | Active | |
|
Sara Lin
sara@acme.co
|
Viewer | never | Invite pending | |
|
Theo Wright
theo@acme.co
|
Editor | 1 week ago | Suspended |
<div class="card">
<div class="card__header card__header--alt">
Team members
<span class="badge badge--soft ms-2">5 of 15 seats</span>
<button type="button" class="btn btn--sm btn--primary ms-auto">
<i data-lucide="user-plus"></i>
Invite
</button>
</div>
<table class="table table--hover table--align-middle mb-0">
<thead>
<tr>
<th scope="col">Member</th>
<th scope="col">Role</th>
<th scope="col">Last active</th>
<th scope="col">Status</th>
<th scope="col" class="text-end">Actions</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<div class="d-flex align-items-center gap-2">
<img src="https://images.unsplash.com/photo-1535713875002-d1d0cf377fde?auto=format&fit=facearea&facepad=2.5&w=120&h=120&q=80" alt="" width="32" height="32" style="border-radius: 50%;" />
<div>
<div>Alex Park</div>
<div class="text-muted-foreground">alex@acme.co</div>
</div>
</div>
</td>
<td><span class="badge badge--soft badge--primary">Owner</span></td>
<td>just now</td>
<td><span class="badge badge--soft badge--success">Active</span></td>
<td class="text-end">
<button type="button" class="btn btn--sm btn--ghost btn--neutral btn--icon-only" aria-label="More">
<i data-lucide="more-horizontal"></i>
</button>
</td>
</tr>
<tr>
<td>
<div class="d-flex align-items-center gap-2">
<img src="https://images.unsplash.com/photo-1494790108377-be9c29b29330?auto=format&fit=facearea&facepad=2.5&w=120&h=120&q=80" alt="" width="32" height="32" style="border-radius: 50%;" />
<div>
<div>Maya Singh</div>
<div class="text-muted-foreground">maya@acme.co</div>
</div>
</div>
</td>
<td><span class="badge badge--soft badge--info">Admin</span></td>
<td>12 min ago</td>
<td><span class="badge badge--soft badge--success">Active</span></td>
<td class="text-end">
<button type="button" class="btn btn--sm btn--ghost btn--neutral btn--icon-only" aria-label="More">
<i data-lucide="more-horizontal"></i>
</button>
</td>
</tr>
<tr>
<td>
<div class="d-flex align-items-center gap-2">
<img src="https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?auto=format&fit=facearea&facepad=2.5&w=120&h=120&q=80" alt="" width="32" height="32" style="border-radius: 50%;" />
<div>
<div>Mateo Reyes</div>
<div class="text-muted-foreground">mateo@acme.co</div>
</div>
</div>
</td>
<td><span class="badge badge--soft">Editor</span></td>
<td>2 hours ago</td>
<td><span class="badge badge--soft badge--success">Active</span></td>
<td class="text-end">
<button type="button" class="btn btn--sm btn--ghost btn--neutral btn--icon-only" aria-label="More">
<i data-lucide="more-horizontal"></i>
</button>
</td>
</tr>
<tr>
<td>
<div class="d-flex align-items-center gap-2">
<img src="https://images.unsplash.com/photo-1438761681033-6461ffad8d80?auto=format&fit=facearea&facepad=2.5&w=120&h=120&q=80" alt="" width="32" height="32" style="border-radius: 50%;" />
<div>
<div>Sara Lin</div>
<div class="text-muted-foreground">sara@acme.co</div>
</div>
</div>
</td>
<td><span class="badge badge--soft">Viewer</span></td>
<td>never</td>
<td><span class="badge badge--soft badge--warning">Invite pending</span></td>
<td class="text-end">
<button type="button" class="btn btn--sm btn--ghost btn--neutral btn--icon-only" aria-label="More">
<i data-lucide="more-horizontal"></i>
</button>
</td>
</tr>
<tr>
<td>
<div class="d-flex align-items-center gap-2">
<img src="https://images.unsplash.com/photo-1500648767791-00dcc994a43e?auto=format&fit=facearea&facepad=2.5&w=120&h=120&q=80" alt="" width="32" height="32" style="border-radius: 50%;" />
<div>
<div>Theo Wright</div>
<div class="text-muted-foreground">theo@acme.co</div>
</div>
</div>
</td>
<td><span class="badge badge--soft">Editor</span></td>
<td>1 week ago</td>
<td><span class="badge badge--soft badge--danger">Suspended</span></td>
<td class="text-end">
<button type="button" class="btn btn--sm btn--ghost btn--neutral btn--icon-only" aria-label="More">
<i data-lucide="more-horizontal"></i>
</button>
</td>
</tr>
</tbody>
</table>
</div>Group divider
Add .table__body--divider to a <tbody> for a heavier rule above it. Helpful when the head reads as a column label rather than a section break.
| Item | Qty | Line total |
|---|---|---|
| Pro seat license | 15 | $435.00 |
| Storage add-on | 2 | $40.00 |
| Priority support | 1 | 99.00 |
<table class="table">
<thead>
<tr>
<th scope="col">Item</th>
<th scope="col">Qty</th>
<th scope="col" class="text-end">Line total</th>
</tr>
</thead>
<tbody class="table__body--divider">
<tr><td>Pro seat license</td><td>15</td><td class="text-end">$435.00</td></tr>
<tr><td>Storage add-on</td><td>2</td><td class="text-end">$40.00</td></tr>
<tr><td>Priority support</td><td>1</td><td class="text-end">99.00</td></tr>
</tbody>
</table>
Responsive
Wrap the table in .table-responsive when it might overflow narrow viewports. The wrapper scrolls horizontally; the table itself stays unchanged.
| Customer | Plan | Seats | MRR | Started | Trial ends | Renews | Owner | Status |
|---|---|---|---|---|---|---|---|---|
| Acme Corp | Business | 48 | ,490 |
Feb 02, 2024 | — | Aug 12 | Maya Singh | Active |
| Riverway Ltd | Team | 14 | $580 | May 14, 2024 | — | Sep 04 | Mateo Reyes | Active |
| Northwind | Enterprise | 112 | $8,200 | Aug 30, 2022 | — | Oct 22 | Alex Park | Active |
| Globex | Team | 9 | $580 | Jun 01, 2024 | Jun 15 | — | Sara Lin | Trialing |
| Initech | Starter | 3 | $0 | Jun 03, 2024 | — | — | Theo Wright | Free |
<div class="table-responsive">
<table class="table">
<thead class="table__head--alt">
<tr>
<th scope="col">Customer</th>
<th scope="col">Plan</th>
<th scope="col">Seats</th>
<th scope="col">MRR</th>
<th scope="col">Started</th>
<th scope="col">Trial ends</th>
<th scope="col">Renews</th>
<th scope="col">Owner</th>
<th scope="col">Status</th>
</tr>
</thead>
<tbody>
<tr>
<td>Acme Corp</td><td>Business</td><td>48</td><td>,490</td>
<td>Feb 02, 2024</td><td>—</td><td>Aug 12</td><td>Maya Singh</td>
<td><span class="badge badge--soft badge--success">Active</span></td>
</tr>
<tr>
<td>Riverway Ltd</td><td>Team</td><td>14</td><td>$580</td>
<td>May 14, 2024</td><td>—</td><td>Sep 04</td><td>Mateo Reyes</td>
<td><span class="badge badge--soft badge--success">Active</span></td>
</tr>
<tr>
<td>Northwind</td><td>Enterprise</td><td>112</td><td>$8,200</td>
<td>Aug 30, 2022</td><td>—</td><td>Oct 22</td><td>Alex Park</td>
<td><span class="badge badge--soft badge--success">Active</span></td>
</tr>
<tr>
<td>Globex</td><td>Team</td><td>9</td><td>$580</td>
<td>Jun 01, 2024</td><td>Jun 15</td><td>—</td><td>Sara Lin</td>
<td><span class="badge badge--soft badge--warning">Trialing</span></td>
</tr>
<tr>
<td>Initech</td><td>Starter</td><td>3</td><td>$0</td>
<td>Jun 03, 2024</td><td>—</td><td>—</td><td>Theo Wright</td>
<td><span class="badge badge--soft">Free</span></td>
</tr>
</tbody>
</table>
</div>
For breakpoint-scoped scrolling, swap to .table-responsive-{sm|md|lg|xl|xxl}. The wrapper switches between scroll and natural layout at the named breakpoint.
Customization
Seventeen variables retune .table without touching component CSS. Override on .table itself, on a parent scope, or on :root, and the cascade scopes the change.
Geometry
| Variable | Default | Use |
|---|---|---|
--table-cell-padding-y |
calc(0.75rem * var(--st-density)) | Vertical cell padding |
--table-cell-padding-x |
calc(0.75rem * var(--st-density)) | Horizontal cell padding |
--table-cell-padding-sm |
calc(0.25rem * var(--st-density)) | Cell padding under .table--sm |
--table-edge-padding |
calc(1.25rem * var(--st-density)) | First and last column inset, lines up with the card body gutter |
--table-group-divider-width |
2px | Rule above .table__body--divider |
Head
| Variable | Default | Use |
|---|---|---|
--table-head-font-size |
0.75rem | Header label size |
--table-head-font-weight |
500 | Header label weight (down from browser-default 700) |
--table-head-color |
var(--st-muted-foreground) | Header label color |
--table-head-bg-alt |
var(--st-surface-2) | Fill applied by .table__head--alt |
Surface
| Variable | Default | Use |
|---|---|---|
--table-color |
var(--st-foreground) | Body text color |
--table-bg |
transparent | Cell background (rest layer) |
--table-border-color |
var(--st-border) | Row and cell border color |
Row states
| Variable | Default | Use |
|---|---|---|
--table-striped-bg |
color-mix(in oklch, var(--st-foreground) 4%, transparent) | Odd row / even column tint |
--table-striped-color |
var(--st-foreground) | Striped row text color |
--table-hover-bg |
var(--st-accent) | Row hover background under .table--hover |
--table-hover-color |
var(--st-accent-foreground) | Row hover text color |
--table-active-bg |
var(--st-highlight) | Selected row background for data-state="active" |
--table-active-color |
var(--st-highlight-foreground) | Selected row text color |
The cell paint chain reads --table-bg-state (hover, active) over --table-bg-type (variant, striped) over --table-bg. Variants set -type and runtime states set -state, so hover lights a striped row without erasing the stripe underneath.