Skip to content

Type to search the docs, API, and blog.

↑↓ navigate↵ openesc close
Zazz 0.7 alpha
API navigation

API

Table

Data tables: alternating rows, grid lines, compact sizing, selection.

Installation

pnpm dlx zazz-ui add table
npx zazz-ui add table
yarn dlx zazz-ui add table
bunx zazz-ui add table

Run zazz-ui init first if the project has no zazz/ folder yet.

Examples

Default

A list of your recent invoices.
Invoice Status Method Amount
INV-001 Paid Credit card $250.00
INV-002 Pending PayPal $150.00
INV-003 Unpaid Bank transfer $350.00
INV-004 Paid Credit card $450.00
Total $1,200.00
<table data-ui="table">
  <caption>
    A list of your recent invoices.
  </caption>
  <thead>
    <tr>
      <th>Invoice</th>
      <th>Status</th>
      <th>Method</th>
      <th style="--text-align: end">Amount</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>INV-001</td>
      <td>Paid</td>
      <td>Credit card</td>
      <td style="--text-align: end">$250.00</td>
    </tr>
    <tr>
      <td>INV-002</td>
      <td>Pending</td>
      <td>PayPal</td>
      <td style="--text-align: end">$150.00</td>
    </tr>
    <tr>
      <td>INV-003</td>
      <td>Unpaid</td>
      <td>Bank transfer</td>
      <td style="--text-align: end">$350.00</td>
    </tr>
    <tr>
      <td>INV-004</td>
      <td>Paid</td>
      <td>Credit card</td>
      <td style="--text-align: end">$450.00</td>
    </tr>
  </tbody>
  <tfoot>
    <tr>
      <td colspan="3">Total</td>
      <td style="--text-align: end">$1,200.00</td>
    </tr>
  </tfoot>
</table>
/**
 * table.css — Table ([data-ui~="table"])
 *
 * @layer      variables, components
 * @requires   _layers.css, _variables.css, _reset.css, _properties.css (--_*-resolved)
 * @uses       oklch(from …) — translucent hover/footer tints
 * @tokens     --ui-table-* (@layer variables; part hooks are read on cells, so presets
 *             set the inheriting hooks rather than privates)
 *
 * Presets: data-table-variant="alternating | grid", data-table-size="sm",
 * data-table-layout="fixed"; on a row data-table-state="selected"; on the
 * caption data-table-caption="top".
 */

@layer variables {
  :root {
    --ui-table-font-family: var(--font-family-body);
    --ui-table-font-size: var(--font-size-sm);

    --ui-table-text: var(--color-foreground);
    --ui-table-bg: transparent;

    /* structural borders — the outer/footer rule, the header separator,
       the tbody row dividers, and the column dividers (grid preset) */
    --ui-table-border: 1px solid var(--color-border);
    --ui-table-head-border: var(--ui-table-border);
    --ui-table-row-border: var(--ui-table-border);
    --ui-table-column-border: none;

    --ui-table-cell-py: calc(var(--spacing) * 2);
    --ui-table-cell-px: calc(var(--spacing) * 2);

    --ui-table-head-h: calc(var(--spacing) * 10);
    --ui-table-head-text: var(--color-foreground);
    --ui-table-head-font-weight: var(--font-weight-strong);

    --ui-table-foot-bg: oklch(from var(--color-muted) l c h / calc(alpha / 2));
    --ui-table-foot-font-weight: var(--font-weight-strong);

    --ui-table-row-bg--hover: oklch(from var(--color-muted) l c h / calc(alpha / 2));
    --ui-table-row-bg--selected: var(--color-muted);

    --ui-table-alternating-bg: var(--color-muted);

    --ui-table-caption-text: var(--color-muted-foreground);
    --ui-table-caption-py: var(--space-xs);
    --ui-table-caption-px: var(--space-xs);
  }
}

@layer ui.components {
  :where([data-ui~="table"]) {
    font-family: var(--_font-family-resolved, var(--ui-table-font-family));
    font-size: var(--_font-size-resolved, var(--ui-table-font-size));
    font-variant-numeric: tabular-nums;

    inline-size: 100%;

    color: var(--_text-resolved, var(--ui-table-text));
    background-color: var(--_bg-resolved, var(--ui-table-bg));

    caption-side: bottom;
    text-align: start;
  }

  :where([data-ui~="table"]) :is(th, td) {
    padding-block: var(--ui-table-cell-py);
    padding-inline: var(--ui-table-cell-px);
    vertical-align: middle;
    text-align: start;
    border-inline: var(--ui-table-column-border);
  }

  :where([data-ui~="table"]) th {
    color: var(--ui-table-head-text);
    font-weight: var(--ui-table-head-font-weight);
  }

  :where([data-ui~="table"]) thead th {
    block-size: var(--ui-table-head-h);
    padding-block: 0;
  }

  :where([data-ui~="table"]) thead tr {
    border-block-end: var(--ui-table-head-border);
  }

  :where([data-ui~="table"]) tbody tr:not(:last-child) {
    border-block-end: var(--ui-table-row-border);
  }

  :where([data-ui~="table"]) tfoot {
    background-color: var(--ui-table-foot-bg);
    font-weight: var(--ui-table-foot-font-weight);
    border-block-start: var(--ui-table-border);
  }

  :where([data-ui~="table"]) caption {
    color: var(--ui-table-caption-text);
    padding-block: var(--ui-table-caption-py);
    padding-inline: var(--ui-table-caption-px);
  }

  /* ===========================================================================
  TABLE LAYOUT & CAPTION SIDE
  =========================================================================== */

  /* omit = table-layout: auto */
  :where([data-ui~="table"][data-table-layout="fixed"]) {
    table-layout: fixed;
  }

  /* omit = caption-side: bottom (set on the table) */
  :where([data-ui~="table"]) caption[data-table-caption="top"] {
    caption-side: top;
  }

  /* ===========================================================================
  TABLE STATES
  =========================================================================== */

  /* hover/selected paint on the cells so they layer above any row stripe;
     disable hover with --ui-table-row-bg--hover: transparent */
  @media (hover: hover) {
    :where([data-ui~="table"]) tbody tr:hover > :is(th, td) {
      background-color: var(--ui-table-row-bg--hover);
    }
  }

  :where([data-ui~="table"]) tbody tr[data-table-state~="selected"] > :is(th, td) {
    background-color: var(--ui-table-row-bg--selected);
  }

  /* ===========================================================================
  TABLE VARIANTS — data-table-variant sets the inheriting part hooks
  =========================================================================== */

  :where([data-ui~="table"][data-table-variant="alternating"]) {
    --ui-table-row-border: none;
  }

  :where([data-ui~="table"][data-table-variant="alternating"]) tbody tr:nth-child(even) {
    background-color: var(--ui-table-alternating-bg);
  }

  :where([data-ui~="table"][data-table-variant="grid"]) {
    --ui-table-column-border: var(--ui-table-border);

    border: var(--ui-table-border);
  }

  /* ===========================================================================
  TABLE SIZES — data-table-size
  =========================================================================== */

  :where([data-ui~="table"][data-table-size="sm"]) {
    --ui-table-cell-py: var(--spacing);
    --ui-table-cell-px: calc(var(--spacing) * 1.5);
    --ui-table-head-h: calc(var(--spacing) * 8);
  }
}

Grid

Plan Seats Storage Price
Starter 3 10 GB $9/mo
Team 10 100 GB $29/mo
Business Unlimited 1 TB $79/mo
<table data-ui="table" data-table-variant="grid">
  <thead>
    <tr>
      <th>Plan</th>
      <th>Seats</th>
      <th>Storage</th>
      <th style="--text-align: end">Price</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Starter</td>
      <td>3</td>
      <td>10 GB</td>
      <td style="--text-align: end">$9/mo</td>
    </tr>
    <tr>
      <td>Team</td>
      <td>10</td>
      <td>100 GB</td>
      <td style="--text-align: end">$29/mo</td>
    </tr>
    <tr>
      <td>Business</td>
      <td>Unlimited</td>
      <td>1 TB</td>
      <td style="--text-align: end">$79/mo</td>
    </tr>
  </tbody>
</table>
/**
 * table.css — Table ([data-ui~="table"])
 *
 * @layer      variables, components
 * @requires   _layers.css, _variables.css, _reset.css, _properties.css (--_*-resolved)
 * @uses       oklch(from …) — translucent hover/footer tints
 * @tokens     --ui-table-* (@layer variables; part hooks are read on cells, so presets
 *             set the inheriting hooks rather than privates)
 *
 * Presets: data-table-variant="alternating | grid", data-table-size="sm",
 * data-table-layout="fixed"; on a row data-table-state="selected"; on the
 * caption data-table-caption="top".
 */

@layer variables {
  :root {
    --ui-table-font-family: var(--font-family-body);
    --ui-table-font-size: var(--font-size-sm);

    --ui-table-text: var(--color-foreground);
    --ui-table-bg: transparent;

    /* structural borders — the outer/footer rule, the header separator,
       the tbody row dividers, and the column dividers (grid preset) */
    --ui-table-border: 1px solid var(--color-border);
    --ui-table-head-border: var(--ui-table-border);
    --ui-table-row-border: var(--ui-table-border);
    --ui-table-column-border: none;

    --ui-table-cell-py: calc(var(--spacing) * 2);
    --ui-table-cell-px: calc(var(--spacing) * 2);

    --ui-table-head-h: calc(var(--spacing) * 10);
    --ui-table-head-text: var(--color-foreground);
    --ui-table-head-font-weight: var(--font-weight-strong);

    --ui-table-foot-bg: oklch(from var(--color-muted) l c h / calc(alpha / 2));
    --ui-table-foot-font-weight: var(--font-weight-strong);

    --ui-table-row-bg--hover: oklch(from var(--color-muted) l c h / calc(alpha / 2));
    --ui-table-row-bg--selected: var(--color-muted);

    --ui-table-alternating-bg: var(--color-muted);

    --ui-table-caption-text: var(--color-muted-foreground);
    --ui-table-caption-py: var(--space-xs);
    --ui-table-caption-px: var(--space-xs);
  }
}

@layer ui.components {
  :where([data-ui~="table"]) {
    font-family: var(--_font-family-resolved, var(--ui-table-font-family));
    font-size: var(--_font-size-resolved, var(--ui-table-font-size));
    font-variant-numeric: tabular-nums;

    inline-size: 100%;

    color: var(--_text-resolved, var(--ui-table-text));
    background-color: var(--_bg-resolved, var(--ui-table-bg));

    caption-side: bottom;
    text-align: start;
  }

  :where([data-ui~="table"]) :is(th, td) {
    padding-block: var(--ui-table-cell-py);
    padding-inline: var(--ui-table-cell-px);
    vertical-align: middle;
    text-align: start;
    border-inline: var(--ui-table-column-border);
  }

  :where([data-ui~="table"]) th {
    color: var(--ui-table-head-text);
    font-weight: var(--ui-table-head-font-weight);
  }

  :where([data-ui~="table"]) thead th {
    block-size: var(--ui-table-head-h);
    padding-block: 0;
  }

  :where([data-ui~="table"]) thead tr {
    border-block-end: var(--ui-table-head-border);
  }

  :where([data-ui~="table"]) tbody tr:not(:last-child) {
    border-block-end: var(--ui-table-row-border);
  }

  :where([data-ui~="table"]) tfoot {
    background-color: var(--ui-table-foot-bg);
    font-weight: var(--ui-table-foot-font-weight);
    border-block-start: var(--ui-table-border);
  }

  :where([data-ui~="table"]) caption {
    color: var(--ui-table-caption-text);
    padding-block: var(--ui-table-caption-py);
    padding-inline: var(--ui-table-caption-px);
  }

  /* ===========================================================================
  TABLE LAYOUT & CAPTION SIDE
  =========================================================================== */

  /* omit = table-layout: auto */
  :where([data-ui~="table"][data-table-layout="fixed"]) {
    table-layout: fixed;
  }

  /* omit = caption-side: bottom (set on the table) */
  :where([data-ui~="table"]) caption[data-table-caption="top"] {
    caption-side: top;
  }

  /* ===========================================================================
  TABLE STATES
  =========================================================================== */

  /* hover/selected paint on the cells so they layer above any row stripe;
     disable hover with --ui-table-row-bg--hover: transparent */
  @media (hover: hover) {
    :where([data-ui~="table"]) tbody tr:hover > :is(th, td) {
      background-color: var(--ui-table-row-bg--hover);
    }
  }

  :where([data-ui~="table"]) tbody tr[data-table-state~="selected"] > :is(th, td) {
    background-color: var(--ui-table-row-bg--selected);
  }

  /* ===========================================================================
  TABLE VARIANTS — data-table-variant sets the inheriting part hooks
  =========================================================================== */

  :where([data-ui~="table"][data-table-variant="alternating"]) {
    --ui-table-row-border: none;
  }

  :where([data-ui~="table"][data-table-variant="alternating"]) tbody tr:nth-child(even) {
    background-color: var(--ui-table-alternating-bg);
  }

  :where([data-ui~="table"][data-table-variant="grid"]) {
    --ui-table-column-border: var(--ui-table-border);

    border: var(--ui-table-border);
  }

  /* ===========================================================================
  TABLE SIZES — data-table-size
  =========================================================================== */

  :where([data-ui~="table"][data-table-size="sm"]) {
    --ui-table-cell-py: var(--spacing);
    --ui-table-cell-px: calc(var(--spacing) * 1.5);
    --ui-table-head-h: calc(var(--spacing) * 8);
  }
}

Alternating

Name Role Location
Ada Lovelace Engineering London
Grace Hopper Engineering New York
Katherine Johnson Research Hampton
Margaret Hamilton Engineering Boston
Annie Easley Research Cleveland
<table data-ui="table" data-table-variant="alternating">
  <thead>
    <tr>
      <th>Name</th>
      <th>Role</th>
      <th>Location</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Ada Lovelace</td>
      <td>Engineering</td>
      <td>London</td>
    </tr>
    <tr>
      <td>Grace Hopper</td>
      <td>Engineering</td>
      <td>New York</td>
    </tr>
    <tr>
      <td>Katherine Johnson</td>
      <td>Research</td>
      <td>Hampton</td>
    </tr>
    <tr>
      <td>Margaret Hamilton</td>
      <td>Engineering</td>
      <td>Boston</td>
    </tr>
    <tr>
      <td>Annie Easley</td>
      <td>Research</td>
      <td>Cleveland</td>
    </tr>
  </tbody>
</table>
/**
 * table.css — Table ([data-ui~="table"])
 *
 * @layer      variables, components
 * @requires   _layers.css, _variables.css, _reset.css, _properties.css (--_*-resolved)
 * @uses       oklch(from …) — translucent hover/footer tints
 * @tokens     --ui-table-* (@layer variables; part hooks are read on cells, so presets
 *             set the inheriting hooks rather than privates)
 *
 * Presets: data-table-variant="alternating | grid", data-table-size="sm",
 * data-table-layout="fixed"; on a row data-table-state="selected"; on the
 * caption data-table-caption="top".
 */

@layer variables {
  :root {
    --ui-table-font-family: var(--font-family-body);
    --ui-table-font-size: var(--font-size-sm);

    --ui-table-text: var(--color-foreground);
    --ui-table-bg: transparent;

    /* structural borders — the outer/footer rule, the header separator,
       the tbody row dividers, and the column dividers (grid preset) */
    --ui-table-border: 1px solid var(--color-border);
    --ui-table-head-border: var(--ui-table-border);
    --ui-table-row-border: var(--ui-table-border);
    --ui-table-column-border: none;

    --ui-table-cell-py: calc(var(--spacing) * 2);
    --ui-table-cell-px: calc(var(--spacing) * 2);

    --ui-table-head-h: calc(var(--spacing) * 10);
    --ui-table-head-text: var(--color-foreground);
    --ui-table-head-font-weight: var(--font-weight-strong);

    --ui-table-foot-bg: oklch(from var(--color-muted) l c h / calc(alpha / 2));
    --ui-table-foot-font-weight: var(--font-weight-strong);

    --ui-table-row-bg--hover: oklch(from var(--color-muted) l c h / calc(alpha / 2));
    --ui-table-row-bg--selected: var(--color-muted);

    --ui-table-alternating-bg: var(--color-muted);

    --ui-table-caption-text: var(--color-muted-foreground);
    --ui-table-caption-py: var(--space-xs);
    --ui-table-caption-px: var(--space-xs);
  }
}

@layer ui.components {
  :where([data-ui~="table"]) {
    font-family: var(--_font-family-resolved, var(--ui-table-font-family));
    font-size: var(--_font-size-resolved, var(--ui-table-font-size));
    font-variant-numeric: tabular-nums;

    inline-size: 100%;

    color: var(--_text-resolved, var(--ui-table-text));
    background-color: var(--_bg-resolved, var(--ui-table-bg));

    caption-side: bottom;
    text-align: start;
  }

  :where([data-ui~="table"]) :is(th, td) {
    padding-block: var(--ui-table-cell-py);
    padding-inline: var(--ui-table-cell-px);
    vertical-align: middle;
    text-align: start;
    border-inline: var(--ui-table-column-border);
  }

  :where([data-ui~="table"]) th {
    color: var(--ui-table-head-text);
    font-weight: var(--ui-table-head-font-weight);
  }

  :where([data-ui~="table"]) thead th {
    block-size: var(--ui-table-head-h);
    padding-block: 0;
  }

  :where([data-ui~="table"]) thead tr {
    border-block-end: var(--ui-table-head-border);
  }

  :where([data-ui~="table"]) tbody tr:not(:last-child) {
    border-block-end: var(--ui-table-row-border);
  }

  :where([data-ui~="table"]) tfoot {
    background-color: var(--ui-table-foot-bg);
    font-weight: var(--ui-table-foot-font-weight);
    border-block-start: var(--ui-table-border);
  }

  :where([data-ui~="table"]) caption {
    color: var(--ui-table-caption-text);
    padding-block: var(--ui-table-caption-py);
    padding-inline: var(--ui-table-caption-px);
  }

  /* ===========================================================================
  TABLE LAYOUT & CAPTION SIDE
  =========================================================================== */

  /* omit = table-layout: auto */
  :where([data-ui~="table"][data-table-layout="fixed"]) {
    table-layout: fixed;
  }

  /* omit = caption-side: bottom (set on the table) */
  :where([data-ui~="table"]) caption[data-table-caption="top"] {
    caption-side: top;
  }

  /* ===========================================================================
  TABLE STATES
  =========================================================================== */

  /* hover/selected paint on the cells so they layer above any row stripe;
     disable hover with --ui-table-row-bg--hover: transparent */
  @media (hover: hover) {
    :where([data-ui~="table"]) tbody tr:hover > :is(th, td) {
      background-color: var(--ui-table-row-bg--hover);
    }
  }

  :where([data-ui~="table"]) tbody tr[data-table-state~="selected"] > :is(th, td) {
    background-color: var(--ui-table-row-bg--selected);
  }

  /* ===========================================================================
  TABLE VARIANTS — data-table-variant sets the inheriting part hooks
  =========================================================================== */

  :where([data-ui~="table"][data-table-variant="alternating"]) {
    --ui-table-row-border: none;
  }

  :where([data-ui~="table"][data-table-variant="alternating"]) tbody tr:nth-child(even) {
    background-color: var(--ui-table-alternating-bg);
  }

  :where([data-ui~="table"][data-table-variant="grid"]) {
    --ui-table-column-border: var(--ui-table-border);

    border: var(--ui-table-border);
  }

  /* ===========================================================================
  TABLE SIZES — data-table-size
  =========================================================================== */

  :where([data-ui~="table"][data-table-size="sm"]) {
    --ui-table-cell-py: var(--spacing);
    --ui-table-cell-px: calc(var(--spacing) * 1.5);
    --ui-table-head-h: calc(var(--spacing) * 8);
  }
}

Size sm

Attribute Kind Values
data-table-variant preset alternating, grid
data-table-size preset sm
data-table-state state selected
<table data-ui="table" data-table-size="sm">
  <thead>
    <tr>
      <th>Attribute</th>
      <th>Kind</th>
      <th>Values</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>data-table-variant</td>
      <td>preset</td>
      <td>alternating, grid</td>
    </tr>
    <tr>
      <td>data-table-size</td>
      <td>preset</td>
      <td>sm</td>
    </tr>
    <tr>
      <td>data-table-state</td>
      <td>state</td>
      <td>selected</td>
    </tr>
  </tbody>
</table>
/**
 * table.css — Table ([data-ui~="table"])
 *
 * @layer      variables, components
 * @requires   _layers.css, _variables.css, _reset.css, _properties.css (--_*-resolved)
 * @uses       oklch(from …) — translucent hover/footer tints
 * @tokens     --ui-table-* (@layer variables; part hooks are read on cells, so presets
 *             set the inheriting hooks rather than privates)
 *
 * Presets: data-table-variant="alternating | grid", data-table-size="sm",
 * data-table-layout="fixed"; on a row data-table-state="selected"; on the
 * caption data-table-caption="top".
 */

@layer variables {
  :root {
    --ui-table-font-family: var(--font-family-body);
    --ui-table-font-size: var(--font-size-sm);

    --ui-table-text: var(--color-foreground);
    --ui-table-bg: transparent;

    /* structural borders — the outer/footer rule, the header separator,
       the tbody row dividers, and the column dividers (grid preset) */
    --ui-table-border: 1px solid var(--color-border);
    --ui-table-head-border: var(--ui-table-border);
    --ui-table-row-border: var(--ui-table-border);
    --ui-table-column-border: none;

    --ui-table-cell-py: calc(var(--spacing) * 2);
    --ui-table-cell-px: calc(var(--spacing) * 2);

    --ui-table-head-h: calc(var(--spacing) * 10);
    --ui-table-head-text: var(--color-foreground);
    --ui-table-head-font-weight: var(--font-weight-strong);

    --ui-table-foot-bg: oklch(from var(--color-muted) l c h / calc(alpha / 2));
    --ui-table-foot-font-weight: var(--font-weight-strong);

    --ui-table-row-bg--hover: oklch(from var(--color-muted) l c h / calc(alpha / 2));
    --ui-table-row-bg--selected: var(--color-muted);

    --ui-table-alternating-bg: var(--color-muted);

    --ui-table-caption-text: var(--color-muted-foreground);
    --ui-table-caption-py: var(--space-xs);
    --ui-table-caption-px: var(--space-xs);
  }
}

@layer ui.components {
  :where([data-ui~="table"]) {
    font-family: var(--_font-family-resolved, var(--ui-table-font-family));
    font-size: var(--_font-size-resolved, var(--ui-table-font-size));
    font-variant-numeric: tabular-nums;

    inline-size: 100%;

    color: var(--_text-resolved, var(--ui-table-text));
    background-color: var(--_bg-resolved, var(--ui-table-bg));

    caption-side: bottom;
    text-align: start;
  }

  :where([data-ui~="table"]) :is(th, td) {
    padding-block: var(--ui-table-cell-py);
    padding-inline: var(--ui-table-cell-px);
    vertical-align: middle;
    text-align: start;
    border-inline: var(--ui-table-column-border);
  }

  :where([data-ui~="table"]) th {
    color: var(--ui-table-head-text);
    font-weight: var(--ui-table-head-font-weight);
  }

  :where([data-ui~="table"]) thead th {
    block-size: var(--ui-table-head-h);
    padding-block: 0;
  }

  :where([data-ui~="table"]) thead tr {
    border-block-end: var(--ui-table-head-border);
  }

  :where([data-ui~="table"]) tbody tr:not(:last-child) {
    border-block-end: var(--ui-table-row-border);
  }

  :where([data-ui~="table"]) tfoot {
    background-color: var(--ui-table-foot-bg);
    font-weight: var(--ui-table-foot-font-weight);
    border-block-start: var(--ui-table-border);
  }

  :where([data-ui~="table"]) caption {
    color: var(--ui-table-caption-text);
    padding-block: var(--ui-table-caption-py);
    padding-inline: var(--ui-table-caption-px);
  }

  /* ===========================================================================
  TABLE LAYOUT & CAPTION SIDE
  =========================================================================== */

  /* omit = table-layout: auto */
  :where([data-ui~="table"][data-table-layout="fixed"]) {
    table-layout: fixed;
  }

  /* omit = caption-side: bottom (set on the table) */
  :where([data-ui~="table"]) caption[data-table-caption="top"] {
    caption-side: top;
  }

  /* ===========================================================================
  TABLE STATES
  =========================================================================== */

  /* hover/selected paint on the cells so they layer above any row stripe;
     disable hover with --ui-table-row-bg--hover: transparent */
  @media (hover: hover) {
    :where([data-ui~="table"]) tbody tr:hover > :is(th, td) {
      background-color: var(--ui-table-row-bg--hover);
    }
  }

  :where([data-ui~="table"]) tbody tr[data-table-state~="selected"] > :is(th, td) {
    background-color: var(--ui-table-row-bg--selected);
  }

  /* ===========================================================================
  TABLE VARIANTS — data-table-variant sets the inheriting part hooks
  =========================================================================== */

  :where([data-ui~="table"][data-table-variant="alternating"]) {
    --ui-table-row-border: none;
  }

  :where([data-ui~="table"][data-table-variant="alternating"]) tbody tr:nth-child(even) {
    background-color: var(--ui-table-alternating-bg);
  }

  :where([data-ui~="table"][data-table-variant="grid"]) {
    --ui-table-column-border: var(--ui-table-border);

    border: var(--ui-table-border);
  }

  /* ===========================================================================
  TABLE SIZES — data-table-size
  =========================================================================== */

  :where([data-ui~="table"][data-table-size="sm"]) {
    --ui-table-cell-py: var(--spacing);
    --ui-table-cell-px: calc(var(--spacing) * 1.5);
    --ui-table-head-h: calc(var(--spacing) * 8);
  }
}

Attributes

table attributes
AttributeKindValuesNotes
data-ui="table"Identityfree valueIdentity token in data-ui.
data-table-captionPresettopPreset: a named bundle of values.
data-table-layoutPresetfixedPreset: a named bundle of values.
data-table-sizePresetsmPreset: a named bundle of values.
data-table-variantPresetalternating gridPreset: a named bundle of values.
data-table-stateStateselectedState the kit's script writes; read it in selectors, never author it.

Hooks

Set a hook on :root, on a subtree, or inline on one element. A state hook (--…--hover) themes that state only; the primitive keeps its other states.

table hooks
HookDefaultState
--ui-table-alternating-bgvar(--color-muted)—
--ui-table-bgtransparent—
--ui-table-border1px solid var(--color-border)—
--ui-table-caption-pxvar(--space-xs)—
--ui-table-caption-pyvar(--space-xs)—
--ui-table-caption-textvar(--color-muted-foreground)—
--ui-table-cell-pxcalc(var(--spacing) * 2)—
--ui-table-cell-pycalc(var(--spacing) * 2)—
--ui-table-column-bordernone—
--ui-table-font-familyvar(--font-family-body)—
--ui-table-font-sizevar(--font-size-sm)—
--ui-table-foot-bgoklch(from var(--color-muted) l c h / calc(alpha / 2))—
--ui-table-foot-font-weightvar(--font-weight-strong)—
--ui-table-head-bordervar(--ui-table-border)—
--ui-table-head-font-weightvar(--font-weight-strong)—
--ui-table-head-hcalc(var(--spacing) * 10)—
--ui-table-head-textvar(--color-foreground)—
--ui-table-row-bg--hoveroklch(from var(--color-muted) l c h / calc(alpha / 2))hover
--ui-table-row-bg--selectedvar(--color-muted)selected
--ui-table-row-bordervar(--ui-table-border)—
--ui-table-textvar(--color-foreground)—