# Formats

> Package `@umriss-ui/table`, version 0.40.0. Demo page: <https://romanhaendler.github.io/umriss-ui/table/formats/>. Every page in one line: [llms.txt](https://romanhaendler.github.io/umriss-ui/table/llms.txt); every page in full: [llms-full.txt](https://romanhaendler.github.io/umriss-ui/table/llms-full.txt).

How a value looks in its cell: a standard format, a presentation of its own, an absent value and a long one.

```ts
import { useTable } from "@umriss-ui/table";
```

Demo page: https://romanhaendler.github.io/umriss-ui/table/formats/

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.

```tsx
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.

```tsx
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.

```tsx
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.

```tsx
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.

```tsx
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](https://romanhaendler.github.io/umriss-ui/table/first-table/#keyboard-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](https://romanhaendler.github.io/umriss-ui/table/first-table/#type-TableProps).

| Prop | Type | Default | Description |
|---|---|---|---|
| [`children`](https://romanhaendler.github.io/umriss-ui/table/first-table/#TableProps-children) | `ReactNode` | — | Columns, `RowDetail`, `RowActions` and the unbound parts. |

#### Accessibility

| Prop | Type | Default | Description |
|---|---|---|---|
| [`ariaLabel`](https://romanhaendler.github.io/umriss-ui/table/first-table/#TableProps-ariaLabel) | `string` | — | Accessible name of the table (not of the outer frame). |

From `TableOptions` — the full table stands on [First table](https://romanhaendler.github.io/umriss-ui/table/first-table/#type-TableOptions).

| Prop | Type | Default | Description |
|---|---|---|---|
| [`rowKey`](https://romanhaendler.github.io/umriss-ui/table/first-table/#TableOptions-rowKey) *required* | `(row: Z) => string` | — | A stable key per row – the basis of selection and expansion. |

From `FieldColumn` — the full table stands on [Column](https://romanhaendler.github.io/umriss-ui/table/column/#type-FieldColumn).

| Prop | Type | Default | Description |
|---|---|---|---|
| [`value`](https://romanhaendler.github.io/umriss-ui/table/column/#FieldColumn-value) *required* | `K` | — | A field of the row. Supplies the column's `id` at the same time. |
| [`id`](https://romanhaendler.github.io/umriss-ui/table/column/#FieldColumn-id) | `string` | — | An id of one's own instead of the field name – for two columns over the same field. |
| [`format`](https://romanhaendler.github.io/umriss-ui/table/column/#FieldColumn-format) | [`FormatFor`](https://romanhaendler.github.io/umriss-ui/table/column/#type-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`](https://romanhaendler.github.io/umriss-ui/table/column/#FieldColumn-children) | [`Presentation`](https://romanhaendler.github.io/umriss-ui/table/column/#type-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](https://romanhaendler.github.io/umriss-ui/table/column/#type-ColumnBase).

| Prop | Type | Default | Description |
|---|---|---|---|
| [`label`](https://romanhaendler.github.io/umriss-ui/table/column/#ColumnBase-label) *required* | `string` | — | Names the column in its header, in the column menu and in the header row of the export. |
| [`rowHeader`](https://romanhaendler.github.io/umriss-ui/table/column/#ColumnBase-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`](https://romanhaendler.github.io/umriss-ui/table/column/#ColumnBase-width) | `number` | the browser's | Initial width in pixels. |
| [`sortable`](https://romanhaendler.github.io/umriss-ui/table/column/#ColumnBase-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](https://romanhaendler.github.io/umriss-ui/table/aggregate/#type-AggregateOptions).

| Prop | Type | Default | Description |
|---|---|---|---|
| [`aggregate`](https://romanhaendler.github.io/umriss-ui/table/aggregate/#AggregateOptions-aggregate) | [`AggregateFor`](https://romanhaendler.github.io/umriss-ui/table/aggregate/#type-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. |
