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
| Region | Req/s | p95 ms |
|---|---|---|
| us-east-2 | 12,906 | 112 |
| eu-west-1 | 4,182 | 84 |
| ap-south-1 | 1,377 | 203 |
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.
| Invoice | Customer | Due | Amount (EUR) |
|---|---|---|---|
| INV-2041 | Harbour Traders | 3 Dec | 1,240.00 |
| INV-2042 | Halcyon Labs | 9 Dec | 86.50 |
| INV-2043 | Fjord Analytics | 14 Dec | 12,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.
| Commit | Branch | Author | Duration |
|---|---|---|---|
a41f9c2 | main | Priya Raman | 1m 52s |
7be03d1 | fix/webhook-retry | Tomás Ferreira | 2m 07s |
c90e4a8 | main | Ingrid Holm | 1m 48s |
15d7b36 | chore/deps | Kwame Mensah | 3m 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.
| Host | Region | CPU % | Memory % | Disk % | Connections |
|---|---|---|---|---|---|
| db-primary-01 | eu-west-1 | 62.4 | 71.0 | 48.2 | 1,204 |
| db-replica-02 | eu-west-1 | 18.9 | 55.3 | 48.1 | 386 |
| db-replica-03 | eu-central-1 | 21.7 | 54.8 | 47.9 | 412 |
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.
| Item | Qty | Price | Amount |
|---|---|---|---|
| Team plan, 12 seats | 12 | $24.00 | $288.00 |
| Audit log retention, 1 year | 1 | $90.00 | $90.00 |
| Priority support | 1 | $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
| Prop | Type | Default | What it does |
|---|---|---|---|
dense | boolean | false | 28px rows and tighter padding instead of 34px, for tables people scan. |
label | string | none | Names the scroll area, which then becomes a region. Use the same words as the caption. |
...rest | ComponentProps<"table"> | none | Every native table attribute. Goes on the table, not the scroll wrapper. |
TableHeader numeric | boolean | false | Right-aligns the header over a numeric column. |
TableHeader sort | "ascending" | "descending" | "none" | none | Sets 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 numeric | boolean | false | Right-aligns with tabular figures so digits line up. |
Pieces ...rest | ComponentProps of caption, thead, tbody, tfoot, tr, th, td | none | Every other piece is the native element with its attributes, including colSpan and rowSpan. |
Anatomy
| scroll | .eq-table-scroll | Wrapper div that scrolls sideways and takes keyboard focus. |
|---|---|---|
| root | .eq-table | The table element. |
| caption | .eq-table-caption | |
| head | .eq-table-head | |
| body | .eq-table-body | |
| foot | .eq-table-foot | TableFoot, a tfoot for totals. A rule above, quiet rows, and the last row strong. |
| row | .eq-table-row | |
| header | .eq-table-header | A th with scope="col" by default. |
| cell | .eq-table-cell | |
| sort | .eq-table-sort | Arrow shown when sort is ascending or descending. Hidden from assistive tech. |
Keyboard
| Tab | Moves focus to the scroll area. |
|---|---|
| Left arrowRight arrow | Scrolls 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);
}
}