How a value looks in its cell: a standard format, a presentation of its own, an absent value and a long one.
import { useTable } from "@umriss-ui/table";
A format or a presentation changes what the cell shows and nothing else: sorting, search, export and totals keep working on the value.
null, undefined and NaN are an absent value: a muted dash, last in either sort direction, counted in no total. The presentation is never called for it.
format names a presentation from the provider's formats; it is typed against the value and keeps sorting, export and totals.
import { useTable } from "@umriss-ui/table";
interface Service {
name: string;
availability: number;
requests: number;
p95: number;
deployed: Date;
}
const SERVICES: Service[] = [
{ name: "Checkout", availability: 0.99921, requests: 1_284_310, p95: 412.37, deployed: new Date(2026, 2, 17, 9, 5) },
{ name: "Billing", availability: 0.99958, requests: 402_118, p95: 238.9, deployed: new Date(2026, 2, 16, 16, 40) },
{ name: "Search", availability: 0.99803, requests: 2_910_455, p95: 186.02, deployed: new Date(2026, 2, 12, 11, 15) },
];
export default function Format() {
const { Table, Column } = useTable(SERVICES, { rowKey: (s) => s.name });
return (
<Table ariaLabel="Services this month">
<Column value="name" label="Service" rowHeader />
<Column value="availability" label="Availability" format="percent" />
<Column value="requests" label="Requests" format="count" />
<Column value="p95" label="p95 (ms)" format={{ decimals: 1 }} />
<Column value="deployed" label="Deployed" format="date" />
{/* A second column over the same field names its own id. */}
<Column id="deployedAt" value="deployed" label="At" format="time" />
</Table>
);
}
children receives the value, typed as the field, and the row; sorting still goes by the value, never by what is drawn.
import { Badge, Meter } from "@umriss-ui/core";
import { useTable } from "@umriss-ui/table";
interface Service {
name: string;
state: "Healthy" | "Degraded" | "Down";
budgetUsed: number;
}
const TONE = { Healthy: "success", Degraded: "warning", Down: "danger" } as const;
const SERVICES: Service[] = [
{ name: "Checkout", state: "Degraded", budgetUsed: 0.86 },
{ name: "Billing", state: "Healthy", budgetUsed: 0.34 },
{ name: "Webhooks", state: "Down", budgetUsed: 1 },
];
export default function Presentation() {
const { Table, Column } = useTable(SERVICES, { rowKey: (s) => s.name });
return (
<Table ariaLabel="Services">
<Column value="name" label="Service" rowHeader />
<Column value="state" label="State">
{(state) => <Badge tone={TONE[state]}>{state}</Badge>}
</Column>
<Column value="budgetUsed" label="Error budget used">
{(share, service) => (
<Meter value={share} tone={share > 0.8 ? "warning" : "accent"} label={`Error budget used, ${service.name}`} showLabel />
)}
</Column>
</Table>
);
}
null, undefined and NaN show a muted dash, sort last and count in no total: the average is over the three estimates given.
import { useTable } from "@umriss-ui/table";
interface WorkItem {
id: string;
name: string;
estimate: number | null;
}
const WORK: WorkItem[] = [
{ id: "w-101", name: "Sign-in with e-mail code", estimate: 18 },
{ id: "w-102", name: "Profile page", estimate: null },
{ id: "w-103", name: "Session handling", estimate: 12 },
{ id: "w-105", name: "Change of address form", estimate: Number.NaN },
{ id: "w-108", name: "Test plan for sign-in", estimate: 10 },
];
export default function AbsentValues() {
const { Table, Column } = useTable(WORK, { rowKey: (w) => w.id });
return (
<Table ariaLabel="Work items">
<Column value="id" label="Item" rowHeader />
<Column value="name" label="Title" />
<Column value="estimate" label="Estimate" aggregate="avg">
{(hours) => `${hours} h`}
</Column>
</Table>
);
}
A long value stays on its line and ends in an ellipsis; point at it, or make it the Active cell, and a tip shows it whole. A column with a width is exactly that wide, and a table wider than its place scrolls in its own frame.
import { useTable } from "@umriss-ui/table";
interface Shipment {
id: string;
customer: string;
address: string;
instructions: string;
weight: number;
}
const SHIPMENTS: Shipment[] = [
{
id: "FP-1004210",
customer: "Holloway Garden Supplies and Landscaping Cooperative",
address: "Unit 14, Riverside Trading Estate, Old Weir Lane",
instructions: "Deliver to the goods entrance at the back; ring twice, the bell in front is out of order.",
weight: 18.4,
},
{ id: "FP-1004223", customer: "Oakridge Pharmacy", address: "2 Market Square", instructions: "", weight: 2.1 },
{
id: "FP-1004236",
customer: "Brixley Cycles",
address: "88 Canal Street",
instructions: "Leave with the neighbour at number 90 if closed.",
weight: 23.7,
},
];
export default function LongValues() {
const { Table, Column } = useTable(SHIPMENTS, { rowKey: (s) => s.id });
return (
<Table ariaLabel="Shipments with delivery instructions">
<Column value="id" label="Shipment" rowHeader width={120} />
<Column value="customer" label="Customer" width={200} />
<Column value="address" label="Address" width={200} />
<Column value="instructions" label="Instructions" width={280} sortable={false} />
<Column value="weight" label="Weight (kg)" format={{ decimals: 1 }} width={110} />
</Table>
);
}
Nothing in a cell wraps and no row grows. Text ends in an ellipsis, and a tip shows it whole; a component that does not fit is cut at the column's edge - give its column room, or show the whole in the row's detail.
import { Badge } from "@umriss-ui/core";
import { useTable } from "@umriss-ui/table";
interface Service {
name: string;
tags: readonly string[];
note: string;
}
const SERVICES: Service[] = [
{ name: "Checkout", tags: ["payments", "eu-west", "critical", "pci"], note: "Card payments time out above 300 ms; the provider's status page says a partial outage." },
{ name: "Billing", tags: ["payments", "eu-west"], note: "Invoices go out on the first working day." },
{ name: "Webhooks", tags: ["integrations", "us-east", "beta"], note: "Retries back off up to an hour; a receiver that answers 410 is dropped." },
];
export default function ValuesThatDoNotFit() {
const { Table, Column } = useTable(SERVICES, { rowKey: (s) => s.name });
return (
<Table ariaLabel="Services with tags and notes">
<Column value="name" label="Service" rowHeader width={120} />
<Column value="tags" label="Tags" width={170} sortable={false}>
{(tags) =>
tags.map((tag) => (
<Badge key={tag} style={{ marginRight: 4 }}>
{tag}
</Badge>
))
}
</Column>
<Column value="note" label="Note" width={300} sortable={false} />
</Table>
);
}
The keys of First table apply here.
A screen reader reads what the cell shows, not the value behind it: a format is read as formatted, and the absent value's dash is spoken as “No value”.
A presentation that draws a component brings that component's role along - a Meter is a meter - and names it per row: give it a label that says the row, “Error budget used, Checkout”, or every cell of the column sounds alike.
From TableProps — the full table stands on First table.
| Name | Type | Default | Description |
|---|---|---|---|
children | ReactNode | — | Columns, RowDetail, RowActions and the unbound parts. |
| Name | Type | Default | Description |
|---|---|---|---|
ariaLabel | string | — | Accessible name of the table (not of the outer frame). |
From TableOptions — the full table stands on First table.
| Name | Type | Default | Description |
|---|---|---|---|
rowKeyrequired | (row: Z) => string | — | A stable key per row – the basis of selection and expansion. |
From FieldColumn — the full table stands on Column.
| Name | Type | Default | Description |
|---|---|---|---|
valuerequired | K | — | A field of the row. Supplies the column's id at the same time. |
id | string | — | An id of one's own instead of the field name – for two columns over the same field. |
format | FormatFor<Z[K]> | — | A standard presentation by name: "percent", "count" or
{ decimals } for numbers, "date", "time" or "dateTime" for
points in time. Alignment, sorting, export and footer stay with the value. |
children | Presentation<Z[K], Z> | — | How the value appears: (value, row). Never called for an absent
value. Required where the value has no text form. |
From ColumnBase — the full table stands on Column.
| Name | Type | Default | Description |
|---|---|---|---|
labelrequired | string | — | Names the column in its header, in the column menu and in the header row of the export. |
rowHeader | boolean | — | This column names the row: row header cells, never hideable, the column
stickyRowHeader pins, and the name by which the row's selection, expander and
actions are read out. At most one per table. |
width | number | the browser's | Initial width in pixels. |
sortable | boolean | true when the value is text, a number, a point in time or a boolean | Whether the column can be sorted by. |
From AggregateOptions — the full table stands on Aggregate.
| Name | Type | Default | Description |
|---|---|---|---|
aggregate | AggregateFor<W, Z> | — | What the column's values come to: in the footer over the filtered set,
in a group's header over its rows. "sum", "avg" for numbers; "min",
"max" for numbers and points in time; "range" for points in time;
"count", "distinct" for every value with a text form - or a function
of one's own. Always from the values, never from other aggregates; absent
values count towards nothing. |