Formats

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.

Examples

Pick a standard format

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>
  );
}

Draw a value with other components

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>
  );
}

Show absent values

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>
  );
}

Fit long values

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>
  );
}

Fit what does not fit

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>
  );
}

Keyboard

The keys of First table apply here.

Accessibility

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.

Props on this page

From TableProps — the full table stands on First table.

NameTypeDefaultDescription
childrenReactNode—Columns, RowDetail, RowActions and the unbound parts.

Accessibility

NameTypeDefaultDescription
ariaLabelstring—Accessible name of the table (not of the outer frame).

From TableOptions — the full table stands on First table.

NameTypeDefaultDescription
rowKeyrequired(row: Z) => string—A stable key per row – the basis of selection and expansion.

From FieldColumn — the full table stands on Column.

NameTypeDefaultDescription
valuerequiredK—A field of the row. Supplies the column's id at the same time.
idstring—An id of one's own instead of the field name – for two columns over the same field.
formatFormatFor<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.
childrenPresentation<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.

NameTypeDefaultDescription
labelrequiredstring—Names the column in its header, in the column menu and in the header row of the export.
rowHeaderboolean—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.
widthnumberthe browser'sInitial width in pixels.
sortablebooleantrue when the value is text, a number, a point in time or a booleanWhether the column can be sorted by.

From AggregateOptions — the full table stands on Aggregate.

NameTypeDefaultDescription
aggregateAggregateFor<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.

Every page of @umriss-ui/table

Getting started

Columns

Finding rows

Grouping

Rows

Around the table

Many rows

Editing

Limits and alarms

API index