Data

Table

Styled native table pieces for tabular data, with row lines, numeric columns and a dense option.

Built on
<table>aria-sort
Client JavaScript
None
Works without React
Yes, as HTML and CSS
Source, gzipped
2.0 kB
Dependencies
React
RegionReq/sp95 ms
us-east-212,906112
eu-west-14,18284
ap-south-11,377203
Try its options

Loading its options.

Code
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/equip/table";

const regions = [
  { name: "us-east-2", rps: "12,906", p95: "112" },
  { name: "eu-west-1", rps: "4,182", p95: "84" },
  { name: "ap-south-1", rps: "1,377", p95: "203" },
];

export default function Demo() {
  return (
    <div style={{ width: 216 }}>
      <Table dense label="Traffic by region" aria-label="Traffic by region">
        <TableHead>
          <TableRow>
            <TableHeader>Region</TableHeader>
            <TableHeader numeric sort="descending">Req/s</TableHeader>
            <TableHeader numeric>p95 ms</TableHeader>
          </TableRow>
        </TableHead>
        <TableBody>
          {regions.map((r) => (
            <TableRow key={r.name}>
              <TableCell>{r.name}</TableCell>
              <TableCell numeric>{r.rps}</TableCell>
              <TableCell numeric>{r.p95}</TableCell>
            </TableRow>
          ))}
        </TableBody>
      </Table>
    </div>
  );
}
Your versionchange its options in Look, This part
HTMLworks in any framework, with the part's CSS
<div style="width:216px">
  <div class="eq-table-scroll" tabindex="0" role="region" aria-label="Traffic by region">
    <table aria-label="Traffic by region" class="eq-table" data-dense="true">
      <thead class="eq-table-head">
        <tr class="eq-table-row">
          <th scope="col" class="eq-table-header">Region</th>
          <th scope="col" aria-sort="descending" class="eq-table-header" data-numeric="true"><svg class="eq-table-sort" viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 4.5 6 7.5l3-3" /></svg>Req/s</th>
          <th scope="col" class="eq-table-header" data-numeric="true">p95 ms</th>
        </tr>
      </thead>
      <tbody class="eq-table-body">
        <tr class="eq-table-row">
          <td class="eq-table-cell">us-east-2</td>
          <td class="eq-table-cell" data-numeric="true">12,906</td>
          <td class="eq-table-cell" data-numeric="true">112</td>
        </tr>
        <tr class="eq-table-row">
          <td class="eq-table-cell">eu-west-1</td>
          <td class="eq-table-cell" data-numeric="true">4,182</td>
          <td class="eq-table-cell" data-numeric="true">84</td>
        </tr>
        <tr class="eq-table-row">
          <td class="eq-table-cell">ap-south-1</td>
          <td class="eq-table-cell" data-numeric="true">1,377</td>
          <td class="eq-table-cell" data-numeric="true">203</td>
        </tr>
      </tbody>
    </table>
  </div>
</div>

Examples

Caption, sort and numbers

Amounts are right-aligned with tabular figures, so the decimal points line up down the column.

Open invoices
InvoiceCustomerDueAmount (EUR)
INV-2041Harbour Traders3 Dec1,240.00
INV-2042Halcyon Labs9 Dec86.50
INV-2043Fjord Analytics14 Dec12,900.00
Code
import { Table, TableBody, TableCaption, TableCell, TableHead, TableHeader, TableRow } from "@/components/equip/table";

const invoices = [
  { id: "INV-2041", customer: "Harbour Traders", due: "3 Dec", amount: "1,240.00" },
  { id: "INV-2042", customer: "Halcyon Labs", due: "9 Dec", amount: "86.50" },
  { id: "INV-2043", customer: "Fjord Analytics", due: "14 Dec", amount: "12,900.00" },
];

export default function Example() {
  return (
    <div style={{ width: "100%", maxWidth: 420, minWidth: 0 }}>
      <Table label="Open invoices">
        <TableCaption>Open invoices</TableCaption>
        <TableHead>
          <TableRow>
            <TableHeader>Invoice</TableHeader>
            <TableHeader>Customer</TableHeader>
            <TableHeader sort="ascending">Due</TableHeader>
            <TableHeader numeric>Amount (EUR)</TableHeader>
          </TableRow>
        </TableHead>
        <TableBody>
          {invoices.map((inv) => (
            <TableRow key={inv.id}>
              <TableCell>{inv.id}</TableCell>
              <TableCell>{inv.customer}</TableCell>
              <TableCell>{inv.due}</TableCell>
              <TableCell numeric>{inv.amount}</TableCell>
            </TableRow>
          ))}
        </TableBody>
      </Table>
    </div>
  );
}
HTMLworks in any framework, with the part's CSS
<div style="width:100%;max-width:420px;min-width:0">
  <div class="eq-table-scroll" tabindex="0" role="region" aria-label="Open invoices">
    <table class="eq-table">
      <caption class="eq-table-caption">Open invoices</caption>
      <thead class="eq-table-head">
        <tr class="eq-table-row">
          <th scope="col" class="eq-table-header">Invoice</th>
          <th scope="col" class="eq-table-header">Customer</th>
          <th scope="col" aria-sort="ascending" class="eq-table-header">Due<svg class="eq-table-sort" viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 7.5 6 4.5l3 3" /></svg></th>
          <th scope="col" class="eq-table-header" data-numeric="true">Amount (EUR)</th>
        </tr>
      </thead>
      <tbody class="eq-table-body">
        <tr class="eq-table-row">
          <td class="eq-table-cell">INV-2041</td>
          <td class="eq-table-cell">Harbour Traders</td>
          <td class="eq-table-cell">3 Dec</td>
          <td class="eq-table-cell" data-numeric="true">1,240.00</td>
        </tr>
        <tr class="eq-table-row">
          <td class="eq-table-cell">INV-2042</td>
          <td class="eq-table-cell">Halcyon Labs</td>
          <td class="eq-table-cell">9 Dec</td>
          <td class="eq-table-cell" data-numeric="true">86.50</td>
        </tr>
        <tr class="eq-table-row">
          <td class="eq-table-cell">INV-2043</td>
          <td class="eq-table-cell">Fjord Analytics</td>
          <td class="eq-table-cell">14 Dec</td>
          <td class="eq-table-cell" data-numeric="true">12,900.00</td>
        </tr>
      </tbody>
    </table>
  </div>
</div>

Dense

Rows drop from 34px to 28px, the height of a small control, for logs and lists people scan.

Recent deploys
CommitBranchAuthorDuration
a41f9c2mainPriya Raman1m 52s
7be03d1fix/webhook-retryTomás Ferreira2m 07s
c90e4a8mainIngrid Holm1m 48s
15d7b36chore/depsKwame Mensah3m 15s
Code
import { Table, TableBody, TableCaption, TableCell, TableHead, TableHeader, TableRow } from "@/components/equip/table";

const deploys = [
  { sha: "a41f9c2", branch: "main", author: "Priya Raman", duration: "1m 52s" },
  { sha: "7be03d1", branch: "fix/webhook-retry", author: "Tomás Ferreira", duration: "2m 07s" },
  { sha: "c90e4a8", branch: "main", author: "Ingrid Holm", duration: "1m 48s" },
  { sha: "15d7b36", branch: "chore/deps", author: "Kwame Mensah", duration: "3m 15s" },
];

export default function Example() {
  return (
    <div style={{ width: "100%", maxWidth: 440, minWidth: 0 }}>
      <Table dense label="Recent deploys">
        <TableCaption>Recent deploys</TableCaption>
        <TableHead>
          <TableRow>
            <TableHeader>Commit</TableHeader>
            <TableHeader>Branch</TableHeader>
            <TableHeader>Author</TableHeader>
            <TableHeader numeric>Duration</TableHeader>
          </TableRow>
        </TableHead>
        <TableBody>
          {deploys.map((d) => (
            <TableRow key={d.sha}>
              <TableCell>
                <code style={{ fontFamily: "var(--eq-font-mono)", fontVariationSettings: "var(--eq-font-mono-settings)" }}>
                  {d.sha}
                </code>
              </TableCell>
              <TableCell>{d.branch}</TableCell>
              <TableCell>{d.author}</TableCell>
              <TableCell numeric>{d.duration}</TableCell>
            </TableRow>
          ))}
        </TableBody>
      </Table>
    </div>
  );
}
HTMLworks in any framework, with the part's CSS
<div style="width:100%;max-width:440px;min-width:0">
  <div class="eq-table-scroll" tabindex="0" role="region" aria-label="Recent deploys">
    <table class="eq-table" data-dense="true">
      <caption class="eq-table-caption">Recent deploys</caption>
      <thead class="eq-table-head">
        <tr class="eq-table-row">
          <th scope="col" class="eq-table-header">Commit</th>
          <th scope="col" class="eq-table-header">Branch</th>
          <th scope="col" class="eq-table-header">Author</th>
          <th scope="col" class="eq-table-header" data-numeric="true">Duration</th>
        </tr>
      </thead>
      <tbody class="eq-table-body">
        <tr class="eq-table-row">
          <td class="eq-table-cell">
            <code style="font-family:var(--eq-font-mono);font-variation-settings:var(--eq-font-mono-settings)">a41f9c2</code>
          </td>
          <td class="eq-table-cell">main</td>
          <td class="eq-table-cell">Priya Raman</td>
          <td class="eq-table-cell" data-numeric="true">1m 52s</td>
        </tr>
        <tr class="eq-table-row">
          <td class="eq-table-cell">
            <code style="font-family:var(--eq-font-mono);font-variation-settings:var(--eq-font-mono-settings)">7be03d1</code>
          </td>
          <td class="eq-table-cell">fix/webhook-retry</td>
          <td class="eq-table-cell">Tomás Ferreira</td>
          <td class="eq-table-cell" data-numeric="true">2m 07s</td>
        </tr>
        <tr class="eq-table-row">
          <td class="eq-table-cell">
            <code style="font-family:var(--eq-font-mono);font-variation-settings:var(--eq-font-mono-settings)">c90e4a8</code>
          </td>
          <td class="eq-table-cell">main</td>
          <td class="eq-table-cell">Ingrid Holm</td>
          <td class="eq-table-cell" data-numeric="true">1m 48s</td>
        </tr>
        <tr class="eq-table-row">
          <td class="eq-table-cell">
            <code style="font-family:var(--eq-font-mono);font-variation-settings:var(--eq-font-mono-settings)">15d7b36</code>
          </td>
          <td class="eq-table-cell">chore/deps</td>
          <td class="eq-table-cell">Kwame Mensah</td>
          <td class="eq-table-cell" data-numeric="true">3m 15s</td>
        </tr>
      </tbody>
    </table>
  </div>
</div>

Wider than its container

The table scrolls sideways inside its own area, which takes focus so keyboard users can scroll it with the arrow keys.

Database nodes
HostRegionCPU %Memory %Disk %Connections
db-primary-01eu-west-162.471.048.21,204
db-replica-02eu-west-118.955.348.1386
db-replica-03eu-central-121.754.847.9412
Code
import { Table, TableBody, TableCaption, TableCell, TableHead, TableHeader, TableRow } from "@/components/equip/table";

const nodes = [
  { host: "db-primary-01", region: "eu-west-1", cpu: "62.4", mem: "71.0", disk: "48.2", conns: "1,204" },
  { host: "db-replica-02", region: "eu-west-1", cpu: "18.9", mem: "55.3", disk: "48.1", conns: "386" },
  { host: "db-replica-03", region: "eu-central-1", cpu: "21.7", mem: "54.8", disk: "47.9", conns: "412" },
];

export default function Example() {
  return (
    <div style={{ width: "100%", maxWidth: 300, minWidth: 0 }}>
      <Table label="Database nodes" style={{ minWidth: 520 }}>
        <TableCaption>Database nodes</TableCaption>
        <TableHead>
          <TableRow>
            <TableHeader>Host</TableHeader>
            <TableHeader>Region</TableHeader>
            <TableHeader numeric>CPU %</TableHeader>
            <TableHeader numeric>Memory %</TableHeader>
            <TableHeader numeric>Disk %</TableHeader>
            <TableHeader numeric>Connections</TableHeader>
          </TableRow>
        </TableHead>
        <TableBody>
          {nodes.map((n) => (
            <TableRow key={n.host}>
              <TableCell>{n.host}</TableCell>
              <TableCell>{n.region}</TableCell>
              <TableCell numeric>{n.cpu}</TableCell>
              <TableCell numeric>{n.mem}</TableCell>
              <TableCell numeric>{n.disk}</TableCell>
              <TableCell numeric>{n.conns}</TableCell>
            </TableRow>
          ))}
        </TableBody>
      </Table>
    </div>
  );
}
HTMLworks in any framework, with the part's CSS
<div style="width:100%;max-width:300px;min-width:0">
  <div class="eq-table-scroll" tabindex="0" role="region" aria-label="Database nodes">
    <table style="min-width:520px" class="eq-table">
      <caption class="eq-table-caption">Database nodes</caption>
      <thead class="eq-table-head">
        <tr class="eq-table-row">
          <th scope="col" class="eq-table-header">Host</th>
          <th scope="col" class="eq-table-header">Region</th>
          <th scope="col" class="eq-table-header" data-numeric="true">CPU %</th>
          <th scope="col" class="eq-table-header" data-numeric="true">Memory %</th>
          <th scope="col" class="eq-table-header" data-numeric="true">Disk %</th>
          <th scope="col" class="eq-table-header" data-numeric="true">Connections</th>
        </tr>
      </thead>
      <tbody class="eq-table-body">
        <tr class="eq-table-row">
          <td class="eq-table-cell">db-primary-01</td>
          <td class="eq-table-cell">eu-west-1</td>
          <td class="eq-table-cell" data-numeric="true">62.4</td>
          <td class="eq-table-cell" data-numeric="true">71.0</td>
          <td class="eq-table-cell" data-numeric="true">48.2</td>
          <td class="eq-table-cell" data-numeric="true">1,204</td>
        </tr>
        <tr class="eq-table-row">
          <td class="eq-table-cell">db-replica-02</td>
          <td class="eq-table-cell">eu-west-1</td>
          <td class="eq-table-cell" data-numeric="true">18.9</td>
          <td class="eq-table-cell" data-numeric="true">55.3</td>
          <td class="eq-table-cell" data-numeric="true">48.1</td>
          <td class="eq-table-cell" data-numeric="true">386</td>
        </tr>
        <tr class="eq-table-row">
          <td class="eq-table-cell">db-replica-03</td>
          <td class="eq-table-cell">eu-central-1</td>
          <td class="eq-table-cell" data-numeric="true">21.7</td>
          <td class="eq-table-cell" data-numeric="true">54.8</td>
          <td class="eq-table-cell" data-numeric="true">47.9</td>
          <td class="eq-table-cell" data-numeric="true">412</td>
        </tr>
      </tbody>
    </table>
  </div>
</div>

Totals in the foot

TableFoot draws the rule above the totals and sets the last row as the total, with the rows before it as quiet breakdown.

Invoice INV-2071
ItemQtyPriceAmount
Team plan, 12 seats12$24.00$288.00
Audit log retention, 1 year1$90.00$90.00
Priority support1$150.00$150.00
Subtotal$528.00
Sales tax, 8.25%$43.56
Total due$571.56
Code
import { Table, TableBody, TableCaption, TableCell, TableFoot, TableHead, TableHeader, TableRow } from "@/components/equip/table";

const lines = [
  { item: "Team plan, 12 seats", quantity: 12, price: 24 },
  { item: "Audit log retention, 1 year", quantity: 1, price: 90 },
  { item: "Priority support", quantity: 1, price: 150 },
];

const usd = new Intl.NumberFormat("en-US", { style: "currency", currency: "USD" }).format;
const subtotal = lines.reduce((sum, l) => sum + l.quantity * l.price, 0);
const tax = Math.round(subtotal * 0.0825 * 100) / 100;

export default function Example() {
  return (
    <div style={{ width: "100%", maxWidth: 440, minWidth: 0 }}>
      <Table label="Invoice INV-2071">
        <TableCaption>Invoice INV-2071</TableCaption>
        <TableHead>
          <TableRow>
            <TableHeader>Item</TableHeader>
            <TableHeader numeric>Qty</TableHeader>
            <TableHeader numeric>Price</TableHeader>
            <TableHeader numeric>Amount</TableHeader>
          </TableRow>
        </TableHead>
        <TableBody>
          {lines.map((l) => (
            <TableRow key={l.item}>
              <TableCell>{l.item}</TableCell>
              <TableCell numeric>{l.quantity}</TableCell>
              <TableCell numeric>{usd(l.price)}</TableCell>
              <TableCell numeric>{usd(l.quantity * l.price)}</TableCell>
            </TableRow>
          ))}
        </TableBody>
        <TableFoot>
          <TableRow>
            <TableHeader scope="row" colSpan={3} numeric>
              Subtotal
            </TableHeader>
            <TableCell numeric>{usd(subtotal)}</TableCell>
          </TableRow>
          <TableRow>
            <TableHeader scope="row" colSpan={3} numeric>
              Sales tax, 8.25%
            </TableHeader>
            <TableCell numeric>{usd(tax)}</TableCell>
          </TableRow>
          <TableRow>
            <TableHeader scope="row" colSpan={3} numeric>
              Total due
            </TableHeader>
            <TableCell numeric>{usd(subtotal + tax)}</TableCell>
          </TableRow>
        </TableFoot>
      </Table>
    </div>
  );
}
HTMLworks in any framework, with the part's CSS
<div style="width:100%;max-width:440px;min-width:0">
  <div class="eq-table-scroll" tabindex="0" role="region" aria-label="Invoice INV-2071">
    <table class="eq-table">
      <caption class="eq-table-caption">Invoice INV-2071</caption>
      <thead class="eq-table-head">
        <tr class="eq-table-row">
          <th scope="col" class="eq-table-header">Item</th>
          <th scope="col" class="eq-table-header" data-numeric="true">Qty</th>
          <th scope="col" class="eq-table-header" data-numeric="true">Price</th>
          <th scope="col" class="eq-table-header" data-numeric="true">Amount</th>
        </tr>
      </thead>
      <tbody class="eq-table-body">
        <tr class="eq-table-row">
          <td class="eq-table-cell">Team plan, 12 seats</td>
          <td class="eq-table-cell" data-numeric="true">12</td>
          <td class="eq-table-cell" data-numeric="true">$24.00</td>
          <td class="eq-table-cell" data-numeric="true">$288.00</td>
        </tr>
        <tr class="eq-table-row">
          <td class="eq-table-cell">Audit log retention, 1 year</td>
          <td class="eq-table-cell" data-numeric="true">1</td>
          <td class="eq-table-cell" data-numeric="true">$90.00</td>
          <td class="eq-table-cell" data-numeric="true">$90.00</td>
        </tr>
        <tr class="eq-table-row">
          <td class="eq-table-cell">Priority support</td>
          <td class="eq-table-cell" data-numeric="true">1</td>
          <td class="eq-table-cell" data-numeric="true">$150.00</td>
          <td class="eq-table-cell" data-numeric="true">$150.00</td>
        </tr>
      </tbody>
      <tfoot class="eq-table-foot">
        <tr class="eq-table-row">
          <th scope="row" colSpan="3" class="eq-table-header" data-numeric="true">Subtotal</th>
          <td class="eq-table-cell" data-numeric="true">$528.00</td>
        </tr>
        <tr class="eq-table-row">
          <th scope="row" colSpan="3" class="eq-table-header" data-numeric="true">Sales tax, 8.25%</th>
          <td class="eq-table-cell" data-numeric="true">$43.56</td>
        </tr>
        <tr class="eq-table-row">
          <th scope="row" colSpan="3" class="eq-table-header" data-numeric="true">Total due</th>
          <td class="eq-table-cell" data-numeric="true">$571.56</td>
        </tr>
      </tfoot>
    </table>
  </div>
</div>

Props

PropTypeDefaultWhat it does
densebooleanfalse28px rows and tighter padding instead of 34px, for tables people scan.
labelstringnoneNames the scroll area, which then becomes a region. Use the same words as the caption.
...restComponentProps<"table">noneEvery native table attribute. Goes on the table, not the scroll wrapper.
TableHeader numericbooleanfalseRight-aligns the header over a numeric column.
TableHeader sort"ascending" | "descending" | "none"noneSets aria-sort and shows an arrow. Display only; sorting is up to you.
TableHeader scope"col" | "row" | "colgroup" | "rowgroup""col"Pass row when the header labels a row.
TableCell numericbooleanfalseRight-aligns with tabular figures so digits line up.
Pieces ...restComponentProps of caption, thead, tbody, tfoot, tr, th, tdnoneEvery other piece is the native element with its attributes, including colSpan and rowSpan.

Anatomy

scroll.eq-table-scrollWrapper div that scrolls sideways and takes keyboard focus.
root.eq-tableThe table element.
caption.eq-table-caption
head.eq-table-head
body.eq-table-body
foot.eq-table-footTableFoot, a tfoot for totals. A rule above, quiet rows, and the last row strong.
row.eq-table-row
header.eq-table-headerA th with scope="col" by default.
cell.eq-table-cell
sort.eq-table-sortArrow shown when sort is ascending or descending. Hidden from assistive tech.

Keyboard

TabMoves focus to the scroll area.
Left arrowRight arrowScrolls a wide table sideways while the scroll area has focus. Native browser behaviour.

Notes

  • These are real table elements, so screen readers offer their table navigation with no extra roles.
  • Give every table a TableCaption; it is the table's accessible name.
  • The scroll area is always a tab stop, because a server part cannot tell whether the table overflows; pass label so that stop has a name.
  • There is no column context, so set numeric on the header and on every cell of a numeric column.
  • Put totals in a TableFoot with scope="row" headers; its last row reads as the total, and the rows before it as the breakdown.
  • sort only describes the current order. To make a column sortable, put a button inside the header and handle the sort in a client component.

Source

Two files, and they are the whole part. Open either to read it, copy it straight from its bar, or add the part to your loadout and install it with everything else you picked.

components/equip/table.tsx102 lines
import type { ComponentProps } from "react";
import "./table.css";

function cx(base: string, className: string | undefined) {
  return [base, className].filter(Boolean).join(" ");
}

export type TableProps = ComponentProps<"table"> & {
  /** Shorter rows and tighter padding, for tables people scan rather than read. */
  dense?: boolean;
  /** Names the scroll area, which is focusable so keyboard users can scroll it. */
  label?: string;
};

export function Table({ dense = false, label, className, ...rest }: TableProps) {
  return (
    <div
      className="eq-table-scroll"
      tabIndex={0}
      role={label ? "region" : undefined}
      aria-label={label}
    >
      <table {...rest} className={cx("eq-table", className)} data-dense={dense || undefined} />
    </div>
  );
}

export function TableCaption({ className, ...rest }: ComponentProps<"caption">) {
  return <caption {...rest} className={cx("eq-table-caption", className)} />;
}

export function TableHead({ className, ...rest }: ComponentProps<"thead">) {
  return <thead {...rest} className={cx("eq-table-head", className)} />;
}

export function TableBody({ className, ...rest }: ComponentProps<"tbody">) {
  return <tbody {...rest} className={cx("eq-table-body", className)} />;
}

/** Totals under the body: a top rule, quiet breakdown rows and a strong last row. */
export function TableFoot({ className, ...rest }: ComponentProps<"tfoot">) {
  return <tfoot {...rest} className={cx("eq-table-foot", className)} />;
}

export function TableRow({ className, ...rest }: ComponentProps<"tr">) {
  return <tr {...rest} className={cx("eq-table-row", className)} />;
}

export type TableHeaderProps = ComponentProps<"th"> & {
  /** Right-aligns the header to sit over a numeric column. Set it on the column's cells too. */
  numeric?: boolean;
  /** Shows the current sort and sets aria-sort. Display only; sorting is the caller's job. */
  sort?: "ascending" | "descending" | "none";
};

export function TableHeader({
  numeric = false,
  sort,
  scope = "col",
  className,
  children,
  ...rest
}: TableHeaderProps) {
  const icon =
    sort === "ascending" || sort === "descending" ? (
      <svg
        className="eq-table-sort"
        viewBox="0 0 12 12"
        fill="none"
        stroke="currentColor"
        strokeWidth="1.5"
        strokeLinecap="round"
        strokeLinejoin="round"
        aria-hidden="true"
      >
        {sort === "ascending" ? <path d="M3 7.5 6 4.5l3 3" /> : <path d="M3 4.5 6 7.5l3-3" />}
      </svg>
    ) : null;
  return (
    <th
      scope={scope}
      aria-sort={sort}
      {...rest}
      className={cx("eq-table-header", className)}
      data-numeric={numeric || undefined}
    >
      {/* In a right-aligned column the arrow leads, so the label lines up with the figures. */}
      {numeric && icon}
      {children}
      {!numeric && icon}
    </th>
  );
}

export type TableCellProps = ComponentProps<"td"> & {
  /** Right-aligns with tabular figures so digits line up down the column. */
  numeric?: boolean;
};

export function TableCell({ numeric = false, className, ...rest }: TableCellProps) {
  return <td {...rest} className={cx("eq-table-cell", className)} data-numeric={numeric || undefined} />;
}
components/equip/table.css116 lines
@layer equip {
  .eq-table-scroll {
    max-width: 100%;
    overflow-x: auto;
    overscroll-behavior-x: contain;
    border-radius: var(--eq-radius-s);
  }

  .eq-table-scroll:focus-visible {
    outline: 2px solid var(--eq-focus);
    outline-offset: 2px;
  }

  .eq-table {
    --_row: var(--eq-h-m);
    --_pad: var(--eq-space-3);

    width: 100%;
    border-collapse: collapse;
    border-spacing: 0;
    color: var(--eq-ink);
    font: var(--eq-weight) var(--eq-text-s) / 1.4 var(--eq-font);
  }

  .eq-table[data-dense] {
    --_row: var(--eq-h-s);
    --_pad: var(--eq-space-2);
  }

  /* Label spec: the caption is the table's title. */
  .eq-table-caption {
    caption-side: top;
    padding-bottom: var(--eq-space-2);
    color: var(--eq-ink);
    font-size: var(--eq-text-s);
    font-weight: var(--eq-weight-strong);
    text-align: start;
  }

  .eq-table-header,
  .eq-table-cell {
    box-sizing: border-box;
    /* On a table cell, height is a minimum: long content still wraps and grows the row. */
    height: var(--_row);
    padding: var(--eq-space-1) var(--_pad);
    border-bottom: 1px solid var(--eq-line);
    text-align: start;
    vertical-align: middle;
  }

  /* No outer padding, so the first and last columns line up with the text around the table. */
  .eq-table-row > :first-child {
    padding-inline-start: 0;
  }

  .eq-table-row > :last-child {
    padding-inline-end: 0;
  }

  .eq-table-header {
    border-bottom-color: var(--eq-line-strong);
    color: var(--eq-ink-2);
    font-size: var(--eq-text-xs);
    font-weight: var(--eq-weight-strong);
    letter-spacing: 0.01em;
    white-space: nowrap;
  }

  .eq-table-header:is([aria-sort="ascending"], [aria-sort="descending"]) {
    color: var(--eq-ink);
  }

  .eq-table-body > .eq-table-row:last-child > * {
    border-bottom: 0;
  }

  /* The foot: a rule above, quiet breakdown rows, and the last row as the total in full ink. */
  .eq-table-foot > .eq-table-row > * {
    border-bottom: 0;
    color: var(--eq-ink-2);
    font-size: var(--eq-text-s);
    font-weight: var(--eq-weight);
    letter-spacing: normal;
  }

  .eq-table-foot > .eq-table-row:first-child > * {
    border-top: 1px solid var(--eq-line-strong);
  }

  .eq-table-foot > .eq-table-row:last-child > * {
    color: var(--eq-ink);
    font-weight: var(--eq-weight-strong);
  }

  /* Under breakdown rows, the total gets its own rule. */
  .eq-table-foot > .eq-table-row:last-child:not(:first-child) > * {
    border-top: 1px solid var(--eq-line-strong);
  }

  :is(.eq-table-header, .eq-table-cell)[data-numeric] {
    text-align: end;
    font-variant-numeric: tabular-nums;
  }

  .eq-table-sort {
    display: inline-block;
    width: 12px;
    height: 12px;
    margin-inline-start: var(--eq-space-1);
    vertical-align: -2px;
  }

  .eq-table-header[data-numeric] > .eq-table-sort {
    margin-inline: 0 var(--eq-space-1);
  }
}

EqUIp is in beta: free for non-commercial use, no warranty, and things can change. Terms

Nothing picked yet. Press A on a part, or use the plus on the board, and the install command builds here.

Loading the index.

Loading the index