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

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-142 ms180 ms12,840
us-west-258 ms220 ms9,210
eu-west-161 ms240 ms7,455
ap-southeast-174 ms310 ms3,902
sa-east-1110 ms420 ms1,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 418502611730
Churned 62715448
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/chargesPOST48,2100.12%
/v1/customersGET31,0890.04%
/v1/invoicesPOST9,4020.31%
/v1/webhooksPOST2,1181.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
mainBump axios to 1.7.4Mateo Reyes0 / 0
feat/billing-v2Add proration previewMaya Singh0 / 14
fix/race-on-replayDrain queue before closeTheo Wright3 / 2
chore/upgrade-node-20Pin engines fieldSara Lin6 / 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-edge2462%71%
prod-core4854%68%
staging618%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-serviceMaya Singh12
auth-gatewayMateo Reyes4
events-pipelineSara Lin7
web-dashboardTheo Wright23
<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.ioMaya · 3 days ago
SMTP_PORT587Maya · 3 days ago
S3_BUCKETstisla-assets-prodTheo · last week
FEATURE_BILLING_V2trueMateo · 1 hour ago
RATE_LIMIT_RPM600Sara · 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-rollupMon 02:00 UTC2 days ago · succeeded
nightly-vacuumDaily 03:30 UTC6 hours ago · succeeded
hourly-replayHourly :1527 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 CorpBusiness$1,490Aug 12
Riverway LtdTeam$580Sep 04
NorthwindEnterprise$8,200Oct 22
GlobexTeam$580Nov 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 license15$435.00
Storage add-on2$40.00
Priority support1$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 CorpBusiness48$1,490 Feb 02, 2024Aug 12Maya Singh Active
Riverway LtdTeam14$580 May 14, 2024Sep 04Mateo Reyes Active
NorthwindEnterprise112$8,200 Aug 30, 2022Oct 22Alex Park Active
GlobexTeam9$580 Jun 01, 2024Jun 15Sara Lin Trialing
InitechStarter3$0 Jun 03, 2024Theo 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

VariableDefaultUse
--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

VariableDefaultUse
--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

VariableDefaultUse
--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

VariableDefaultUse
--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
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-142 ms180 ms12,840
us-west-258 ms220 ms9,210
eu-west-161 ms240 ms7,455
ap-southeast-174 ms310 ms3,902
sa-east-1110 ms420 ms1,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 418502611730
Churned 62715448
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/chargesPOST48,2100.12%
/v1/customersGET31,0890.04%
/v1/invoicesPOST9,4020.31%
/v1/webhooksPOST2,1181.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
mainBump axios to 1.7.4Mateo Reyes0 / 0
feat/billing-v2Add proration previewMaya Singh0 / 14
fix/race-on-replayDrain queue before closeTheo Wright3 / 2
chore/upgrade-node-20Pin engines fieldSara Lin6 / 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-edge2462%71%
prod-core4854%68%
staging618%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-serviceMaya Singh12
auth-gatewayMateo Reyes4
events-pipelineSara Lin7
web-dashboardTheo Wright23
<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.ioMaya · 3 days ago
SMTP_PORT587Maya · 3 days ago
S3_BUCKETstisla-assets-prodTheo · last week
FEATURE_BILLING_V2trueMateo · 1 hour ago
RATE_LIMIT_RPM600Sara · 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-rollupMon 02:00 UTC2 days ago · succeeded
nightly-vacuumDaily 03:30 UTC6 hours ago · succeeded
hourly-replayHourly :1527 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 CorpBusiness$1,490Aug 12
Riverway LtdTeam$580Sep 04
NorthwindEnterprise$8,200Oct 22
GlobexTeam$580Nov 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 license15$435.00
Storage add-on2$40.00
Priority support1$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 CorpBusiness48$1,490 Feb 02, 2024Aug 12Maya Singh Active
Riverway LtdTeam14$580 May 14, 2024Sep 04Mateo Reyes Active
NorthwindEnterprise112$8,200 Aug 30, 2022Oct 22Alex Park Active
GlobexTeam9$580 Jun 01, 2024Jun 15Sara Lin Trialing
InitechStarter3$0 Jun 03, 2024Theo 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

VariableDefaultUse
--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

VariableDefaultUse
--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

VariableDefaultUse
--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

VariableDefaultUse
--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-142 ms180 ms12,840
us-west-258 ms220 ms9,210
eu-west-161 ms240 ms7,455
ap-southeast-174 ms310 ms3,902
sa-east-1110 ms420 ms1,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 418502611730
Churned 62715448
Net new ARR
82k

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-142 ms180 ms12,840
us-west-258 ms220 ms9,210
eu-west-161 ms240 ms7,455
ap-southeast-174 ms310 ms3,902
sa-east-1110 ms420 ms1,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 418502611730
Churned 62715448
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/chargesPOST48,2100.12%
/v1/customersGET31,0890.04%
/v1/invoicesPOST9,4020.31%
/v1/webhooksPOST2,1181.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
mainBump axios to 1.7.4Mateo Reyes0 / 0
feat/billing-v2Add proration previewMaya Singh0 / 14
fix/race-on-replayDrain queue before closeTheo Wright3 / 2
chore/upgrade-node-20Pin engines fieldSara Lin6 / 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-edge2462%71%
prod-core4854%68%
staging618%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-serviceMaya Singh12
auth-gatewayMateo Reyes4
events-pipelineSara Lin7
web-dashboardTheo Wright23
<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.ioMaya · 3 days ago
SMTP_PORT587Maya · 3 days ago
S3_BUCKETstisla-assets-prodTheo · last week
FEATURE_BILLING_V2trueMateo · 1 hour ago
RATE_LIMIT_RPM600Sara · 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-rollupMon 02:00 UTC2 days ago · succeeded
nightly-vacuumDaily 03:30 UTC6 hours ago · succeeded
hourly-replayHourly :1527 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 CorpBusiness$1,490Aug 12
Riverway LtdTeam$580Sep 04
NorthwindEnterprise$8,200Oct 22
GlobexTeam$580Nov 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 license15$435.00
Storage add-on2$40.00
Priority support1$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 CorpBusiness48$1,490 Feb 02, 2024Aug 12Maya Singh Active
Riverway LtdTeam14$580 May 14, 2024Sep 04Mateo Reyes Active
NorthwindEnterprise112$8,200 Aug 30, 2022Oct 22Alex Park Active
GlobexTeam9$580 Jun 01, 2024Jun 15Sara Lin Trialing
InitechStarter3$0 Jun 03, 2024Theo 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

VariableDefaultUse
--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

VariableDefaultUse
--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

VariableDefaultUse
--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

VariableDefaultUse
--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

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-142 ms180 ms12,840
us-west-258 ms220 ms9,210
eu-west-161 ms240 ms7,455
ap-southeast-174 ms310 ms3,902
sa-east-1110 ms420 ms1,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 418502611730
Churned 62715448
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/chargesPOST48,2100.12%
/v1/customersGET31,0890.04%
/v1/invoicesPOST9,4020.31%
/v1/webhooksPOST2,1181.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
mainBump axios to 1.7.4Mateo Reyes0 / 0
feat/billing-v2Add proration previewMaya Singh0 / 14
fix/race-on-replayDrain queue before closeTheo Wright3 / 2
chore/upgrade-node-20Pin engines fieldSara Lin6 / 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-edge2462%71%
prod-core4854%68%
staging618%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-serviceMaya Singh12
auth-gatewayMateo Reyes4
events-pipelineSara Lin7
web-dashboardTheo Wright23
<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.ioMaya · 3 days ago
SMTP_PORT587Maya · 3 days ago
S3_BUCKETstisla-assets-prodTheo · last week
FEATURE_BILLING_V2trueMateo · 1 hour ago
RATE_LIMIT_RPM600Sara · 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-rollupMon 02:00 UTC2 days ago · succeeded
nightly-vacuumDaily 03:30 UTC6 hours ago · succeeded
hourly-replayHourly :1527 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 CorpBusiness$1,490Aug 12
Riverway LtdTeam$580Sep 04
NorthwindEnterprise$8,200Oct 22
GlobexTeam$580Nov 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 license15$435.00
Storage add-on2$40.00
Priority support1$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 CorpBusiness48$1,490 Feb 02, 2024Aug 12Maya Singh Active
Riverway LtdTeam14$580 May 14, 2024Sep 04Mateo Reyes Active
NorthwindEnterprise112$8,200 Aug 30, 2022Oct 22Alex Park Active
GlobexTeam9$580 Jun 01, 2024Jun 15Sara Lin Trialing
InitechStarter3$0 Jun 03, 2024Theo 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

VariableDefaultUse
--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

VariableDefaultUse
--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

VariableDefaultUse
--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

VariableDefaultUse
--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
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-142 ms180 ms12,840
us-west-258 ms220 ms9,210
eu-west-161 ms240 ms7,455
ap-southeast-174 ms310 ms3,902
sa-east-1110 ms420 ms1,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 418502611730
Churned 62715448
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/chargesPOST48,2100.12%
/v1/customersGET31,0890.04%
/v1/invoicesPOST9,4020.31%
/v1/webhooksPOST2,1181.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
mainBump axios to 1.7.4Mateo Reyes0 / 0
feat/billing-v2Add proration previewMaya Singh0 / 14
fix/race-on-replayDrain queue before closeTheo Wright3 / 2
chore/upgrade-node-20Pin engines fieldSara Lin6 / 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-edge2462%71%
prod-core4854%68%
staging618%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-serviceMaya Singh12
auth-gatewayMateo Reyes4
events-pipelineSara Lin7
web-dashboardTheo Wright23
<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.ioMaya · 3 days ago
SMTP_PORT587Maya · 3 days ago
S3_BUCKETstisla-assets-prodTheo · last week
FEATURE_BILLING_V2trueMateo · 1 hour ago
RATE_LIMIT_RPM600Sara · 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-rollupMon 02:00 UTC2 days ago · succeeded
nightly-vacuumDaily 03:30 UTC6 hours ago · succeeded
hourly-replayHourly :1527 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 CorpBusiness$1,490Aug 12
Riverway LtdTeam$580Sep 04
NorthwindEnterprise$8,200Oct 22
GlobexTeam$580Nov 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 license15$435.00
Storage add-on2$40.00
Priority support1$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 CorpBusiness48$1,490 Feb 02, 2024Aug 12Maya Singh Active
Riverway LtdTeam14$580 May 14, 2024Sep 04Mateo Reyes Active
NorthwindEnterprise112$8,200 Aug 30, 2022Oct 22Alex Park Active
GlobexTeam9$580 Jun 01, 2024Jun 15Sara Lin Trialing
InitechStarter3$0 Jun 03, 2024Theo 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

VariableDefaultUse
--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

VariableDefaultUse
--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

VariableDefaultUse
--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

VariableDefaultUse
--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-142 ms180 ms12,840
us-west-258 ms220 ms9,210
eu-west-161 ms240 ms7,455
ap-southeast-174 ms310 ms3,902
sa-east-1110 ms420 ms1,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 418502611730
Churned 62715448
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/chargesPOST48,2100.12%
/v1/customersGET31,0890.04%
/v1/invoicesPOST9,4020.31%
/v1/webhooksPOST2,1181.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
mainBump axios to 1.7.4Mateo Reyes0 / 0
feat/billing-v2Add proration previewMaya Singh0 / 14
fix/race-on-replayDrain queue before closeTheo Wright3 / 2
chore/upgrade-node-20Pin engines fieldSara Lin6 / 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-edge2462%71%
prod-core4854%68%
staging618%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-serviceMaya Singh12
auth-gatewayMateo Reyes4
events-pipelineSara Lin7
web-dashboardTheo Wright23
<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.ioMaya · 3 days ago
SMTP_PORT587Maya · 3 days ago
S3_BUCKETstisla-assets-prodTheo · last week
FEATURE_BILLING_V2trueMateo · 1 hour ago
RATE_LIMIT_RPM600Sara · 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-rollupMon 02:00 UTC2 days ago · succeeded
nightly-vacuumDaily 03:30 UTC6 hours ago · succeeded
hourly-replayHourly :1527 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 CorpBusiness$1,490Aug 12
Riverway LtdTeam$580Sep 04
NorthwindEnterprise$8,200Oct 22
GlobexTeam$580Nov 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 license15$435.00
Storage add-on2$40.00
Priority support1$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 CorpBusiness48$1,490 Feb 02, 2024Aug 12Maya Singh Active
Riverway LtdTeam14$580 May 14, 2024Sep 04Mateo Reyes Active
NorthwindEnterprise112$8,200 Aug 30, 2022Oct 22Alex Park Active
GlobexTeam9$580 Jun 01, 2024Jun 15Sara Lin Trialing
InitechStarter3$0 Jun 03, 2024Theo 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

VariableDefaultUse
--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

VariableDefaultUse
--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

VariableDefaultUse
--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

VariableDefaultUse
--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/chargesPOST48,2100.12%
/v1/customersGET31,0890.04%
/v1/invoicesPOST9,4020.31%
/v1/webhooksPOST2,1181.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
mainBump axios to 1.7.4Mateo Reyes0 / 0
feat/billing-v2Add proration previewMaya Singh0 / 14
fix/race-on-replayDrain queue before closeTheo Wright3 / 2
chore/upgrade-node-20Pin engines fieldSara Lin6 / 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-edge2462%71%
prod-core4854%68%
staging618%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-serviceMaya Singh12
auth-gatewayMateo Reyes4
events-pipelineSara Lin7
web-dashboardTheo Wright23
<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.ioMaya · 3 days ago
SMTP_PORT587Maya · 3 days ago
S3_BUCKETstisla-assets-prodTheo · last week
FEATURE_BILLING_V2trueMateo · 1 hour ago
RATE_LIMIT_RPM600Sara · 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-rollupMon 02:00 UTC2 days ago · succeeded
nightly-vacuumDaily 03:30 UTC6 hours ago · succeeded
hourly-replayHourly :1527 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 CorpBusiness
,490
Aug 12
Riverway LtdTeam$580Sep 04
NorthwindEnterprise$8,200Oct 22
GlobexTeam$580Nov 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

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-142 ms180 ms12,840
us-west-258 ms220 ms9,210
eu-west-161 ms240 ms7,455
ap-southeast-174 ms310 ms3,902
sa-east-1110 ms420 ms1,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 418502611730
Churned 62715448
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/chargesPOST48,2100.12%
/v1/customersGET31,0890.04%
/v1/invoicesPOST9,4020.31%
/v1/webhooksPOST2,1181.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
mainBump axios to 1.7.4Mateo Reyes0 / 0
feat/billing-v2Add proration previewMaya Singh0 / 14
fix/race-on-replayDrain queue before closeTheo Wright3 / 2
chore/upgrade-node-20Pin engines fieldSara Lin6 / 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-edge2462%71%
prod-core4854%68%
staging618%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-serviceMaya Singh12
auth-gatewayMateo Reyes4
events-pipelineSara Lin7
web-dashboardTheo Wright23
<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.ioMaya · 3 days ago
SMTP_PORT587Maya · 3 days ago
S3_BUCKETstisla-assets-prodTheo · last week
FEATURE_BILLING_V2trueMateo · 1 hour ago
RATE_LIMIT_RPM600Sara · 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-rollupMon 02:00 UTC2 days ago · succeeded
nightly-vacuumDaily 03:30 UTC6 hours ago · succeeded
hourly-replayHourly :1527 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 CorpBusiness$1,490Aug 12
Riverway LtdTeam$580Sep 04
NorthwindEnterprise$8,200Oct 22
GlobexTeam$580Nov 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 license15$435.00
Storage add-on2$40.00
Priority support1$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 CorpBusiness48$1,490 Feb 02, 2024Aug 12Maya Singh Active
Riverway LtdTeam14$580 May 14, 2024Sep 04Mateo Reyes Active
NorthwindEnterprise112$8,200 Aug 30, 2022Oct 22Alex Park Active
GlobexTeam9$580 Jun 01, 2024Jun 15Sara Lin Trialing
InitechStarter3$0 Jun 03, 2024Theo 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

VariableDefaultUse
--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

VariableDefaultUse
--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

VariableDefaultUse
--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

VariableDefaultUse
--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
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-142 ms180 ms12,840
us-west-258 ms220 ms9,210
eu-west-161 ms240 ms7,455
ap-southeast-174 ms310 ms3,902
sa-east-1110 ms420 ms1,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 418502611730
Churned 62715448
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/chargesPOST48,2100.12%
/v1/customersGET31,0890.04%
/v1/invoicesPOST9,4020.31%
/v1/webhooksPOST2,1181.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
mainBump axios to 1.7.4Mateo Reyes0 / 0
feat/billing-v2Add proration previewMaya Singh0 / 14
fix/race-on-replayDrain queue before closeTheo Wright3 / 2
chore/upgrade-node-20Pin engines fieldSara Lin6 / 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-edge2462%71%
prod-core4854%68%
staging618%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-serviceMaya Singh12
auth-gatewayMateo Reyes4
events-pipelineSara Lin7
web-dashboardTheo Wright23
<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.ioMaya · 3 days ago
SMTP_PORT587Maya · 3 days ago
S3_BUCKETstisla-assets-prodTheo · last week
FEATURE_BILLING_V2trueMateo · 1 hour ago
RATE_LIMIT_RPM600Sara · 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-rollupMon 02:00 UTC2 days ago · succeeded
nightly-vacuumDaily 03:30 UTC6 hours ago · succeeded
hourly-replayHourly :1527 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 CorpBusiness$1,490Aug 12
Riverway LtdTeam$580Sep 04
NorthwindEnterprise$8,200Oct 22
GlobexTeam$580Nov 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 license15$435.00
Storage add-on2$40.00
Priority support1$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 CorpBusiness48$1,490 Feb 02, 2024Aug 12Maya Singh Active
Riverway LtdTeam14$580 May 14, 2024Sep 04Mateo Reyes Active
NorthwindEnterprise112$8,200 Aug 30, 2022Oct 22Alex Park Active
GlobexTeam9$580 Jun 01, 2024Jun 15Sara Lin Trialing
InitechStarter3$0 Jun 03, 2024Theo 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

VariableDefaultUse
--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

VariableDefaultUse
--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

VariableDefaultUse
--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

VariableDefaultUse
--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.

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 license15$435.00
Storage add-on2$40.00
Priority support1
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 CorpBusiness48
,490
Feb 02, 2024Aug 12Maya Singh Active
Riverway LtdTeam14$580 May 14, 2024Sep 04Mateo Reyes Active
NorthwindEnterprise112$8,200 Aug 30, 2022Oct 22Alex Park Active
GlobexTeam9$580 Jun 01, 2024Jun 15Sara Lin Trialing
InitechStarter3$0 Jun 03, 2024Theo 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

VariableDefaultUse
--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
VariableDefaultUse
--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

VariableDefaultUse
--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

VariableDefaultUse
--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.