Column

One value per row, read from a field or computed. Everything the table does with a column (sort, search, export, total) works on that value, never on what the cell shows.

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

Without a presentation the value type decides: text on the left, numbers right-aligned with tabular figures, points in time as date and time, truth values as a word.

A computed value needs an id. One column is the row header: it names the row, cannot be hidden, and the row's checkbox and actions are read out with it.

A computed value (like sortValue, groupValue and rowKey) may be written inline: a new function with the same source text counts as the same one, and nothing is recomputed. A closure that reads an outer variable which has changed reads as unchanged; give the function a new text, or carry the variable in the row data.

Examples

Read a value from a field

value names a field of the row, and the compiler offers only the fields there are; the field name is the column's id as well.

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

interface Vehicle {
  plate: string;
  type: string;
  capacity: number;
}

const VEHICLES: Vehicle[] = [
  { plate: "FP 214 K", type: "van", capacity: 1200 },
  { plate: "FP 377 K", type: "e-van", capacity: 900 },
  { plate: "FP 118 R", type: "truck", capacity: 7500 },
];

export default function ValueFromField() {
  const { Table, Column } = useTable(VEHICLES, { rowKey: (v) => v.plate });

  return (
    <Table ariaLabel="Vehicles">
      <Column value="plate" label="Plate" rowHeader />
      <Column value="type" label="Type" />
      <Column value="capacity" label="Payload (kg)" />
    </Table>
  );
}

Compute a value from the row

When value is a function the column needs an id; the computed value sorts, formats and exports like any other.

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

interface Line {
  description: string;
  quantity: number;
  unitPrice: number;
}

const LINES: Line[] = [
  { description: "Desk lamps, LED", quantity: 24, unitPrice: 48.9 },
  { description: "Printer paper, box of 5 reams", quantity: 40, unitPrice: 21.5 },
  { description: "Delivery", quantity: 1, unitPrice: 35 },
];

export default function ComputedValue() {
  const { Table, Column } = useTable(LINES, { rowKey: (l) => l.description });

  return (
    <Table ariaLabel="Invoice lines">
      <Column value="description" label="Description" rowHeader />
      <Column value="quantity" label="Quantity" />
      <Column value="unitPrice" label="Unit price (€)" format={{ decimals: 2 }} />
      <Column id="net" label="Net (€)" value={(l) => l.quantity * l.unitPrice} format={{ decimals: 2 }} />
    </Table>
  );
}

Let the value type decide

Without children the value decides: text left, numbers right with tabular figures, dates as date and time, booleans as a word.

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

interface Service {
  name: string;
  requests: number;
  deployed: Date;
  pages: boolean;
  openIncidents: number | null;
}

const SERVICES: Service[] = [
  { name: "Checkout", requests: 1840.5, deployed: new Date(2026, 2, 17, 9, 5), pages: true, openIncidents: 1 },
  { name: "Reporting", requests: 18, deployed: new Date(2026, 2, 16, 14, 30), pages: false, openIncidents: null },
];

export default function Defaults() {
  const { Table, Column } = useTable(SERVICES, { rowKey: (s) => s.name });

  return (
    <Table ariaLabel="Defaults per value type">
      <Column value="name" label="Text" rowHeader />
      <Column value="requests" label="Number" />
      <Column value="deployed" label="Point in time" />
      <Column value="pages" label="Truth value" />
      <Column value="openIncidents" label="Absent" />
    </Table>
  );
}

Name the row

rowHeader makes a column the row's name: it cannot be hidden, and the row's checkbox and actions are read out with it.

import { useState } from "react";
import { Stack, Text } from "@umriss-ui/core";
import { ColumnMenu, Toolbar, useTable } from "@umriss-ui/table";

interface Incident {
  id: string;
  title: string;
}

const INCIDENTS: Incident[] = [
  { id: "INC-1048", title: "Checkout slow, card payments time out" },
  { id: "INC-1047", title: "Webhook deliveries delayed" },
  { id: "INC-1046", title: "Thumbnails missing for new uploads" },
];

export default function RowHeader() {
  const [last, setLast] = useState("Nothing opened yet.");
  const { Table, Column, RowActions, Action } = useTable(INCIDENTS, { rowKey: (i) => i.id });

  return (
    <Stack gap={3}>
      <Table selectable ariaLabel="Incidents">
        <Toolbar>
          <ColumnMenu />
        </Toolbar>
        <Column value="id" label="Incident" rowHeader />
        <Column value="title" label="Title" />
        <RowActions>
          <Action onSelect={(i) => setLast(`${i.id} opened`)}>Open</Action>
        </RowActions>
      </Table>
      <Text size="sm" tone="secondary" role="status">
        {last}
      </Text>
    </Stack>
  );
}

When to use something else

Keyboard

The keys of First table, ColumnMenu, RowActions and Checkbox apply here.

Accessibility

The label is the column header's text, and so what a screen reader says for every cell under it. The rowHeader column's cells are row headers: a reader names the row by them, and the row's checkbox and actions are named after them - “Select INC-1048”, “Open: INC-1048”. Without one, each of those says only its own word.

API

FieldColumn<Z, K extends Field<Z>>

NameTypeDefaultDescription
valuerequiredK—A field of the row. Supplies the column's id at the same time.

Shown in: Read a value from a field, Compute a value from the row, Let the value type decide and 118 more

idstring—An id of one's own instead of the field name – for two columns over the same field.

Shown in: Pick a standard format (Formats), Share a column between tables (Presets)

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.

Shown in: Compute a value from the row, Set wording and formats once (Provider), Pick a standard format (Formats) and 33 more

filterFilterFor<Z[K]>—A filter in the column header: "list" offers the values that occur, "range" two bounds (numbers or points in time), a filter from columnFilter asks whatever it asks itself. Its condition stands in the table toolbar.

Shown in: A label wider than its column (Width and pinning), Share a column between tables (Presets), Choose from the values that occur (Filter) and 11 more

groupGroupFor<Z[K]>—Groups points in time by "day", "week", "month" or "year".

Shown in: Share a column between tables (Presets), Group dates by month (Grouping)

editEditFor<Z[K], Z>—Edits the column's cells in place in grid mode: Enter, F2 or typing starts it, Enter or Tab commits, Escape cancels. The table applies nothing - the edit is reported through the table's onCellEdit. from EditOptions

Shown in: Share a column between tables (Presets), Combine grid mode with groups, pinning and many rows (Grid mode), Edit a comments column (Edits) and 6 more

editOptionsreadonly Present<Z[K]>[]the values that occurWhat edit="select" offers. from EditOptions

Shown in: Share a column between tables (Presets), Check an edit before it is reported (Edits), Save a row on purpose (Edits) and 1 more

validate(value: Z[K] | null, row: Z) => string | null | undefined—Checks a draft before it is reported: a message keeps the editor open and stands beneath the field, as in a FormField; nothing lets it through. from EditOptions

Shown in: Share a column between tables (Presets), Combine grid mode with groups, pinning and many rows (Grid mode), Check an edit before it is reported (Edits) and 3 more

childrenPresentation<Z[K], Z>—How the value appears: (value, row). Never called for an absent value. Required where the value has no text form.

Shown in: Draw a value with other components (Formats), Show absent values (Formats), Fit what does not fit (Formats) and 9 more

Also every prop of ColumnBase, ValuePaths and AggregateOptions.

ColumnBase

NameTypeDefaultDescription
labelrequiredstring—Names the column in its header, in the column menu and in the header row of the export.

Shown in: Read a value from a field, Compute a value from the row, Let the value type decide and 118 more

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.

Shown in: Read a value from a field, Compute a value from the row, Let the value type decide and 117 more

numericbooleandecided by the first value presentRight-aligned with tabular figures.

Shown in: Share a column between tables (Presets)

widthnumberthe browser'sInitial width in pixels.

Shown in: Fit long values (Formats), Fit what does not fit (Formats), Let people set the width (Width and pinning) and 15 more

resizableboolean—Shows the drag grip on the header cell.

Shown in: Let people set the width (Width and pinning), Pin and size from controls of your own (Width and pinning), Share a column between tables (Presets) and 1 more

pinPin
"start" | "end"
—Keeps the column in view while the table scrolls sideways: in a block before every other column, or after them. The user can change it in the column menu; the view carries the change. Pinned blocks take at most half of the width the table shows: on a narrower one the end block, then the start block scrolls with the rest, and sticks again when there is room.

Shown in: Pin columns to both sides (Width and pinning), Share a column between tables (Presets), Combine grid mode with groups, pinning and many rows (Grid mode) and 1 more

sortablebooleantrue when the value is text, a number, a point in time or a booleanWhether the column can be sorted by.

Shown in: Fit long values (Formats), Fit what does not fit (Formats), Let people set the width (Width and pinning) and 6 more

searchablebooleantrue for text, false otherwiseTakes part in the search.

Shown in: Share a column between tables (Presets), Leave a column out (Search), An uneven organisation (Tree rows)

groupablebooleantrue when its value is text, a number, a point in time or a boolean, or it has a groupValueCan the table be grouped by this column?

Shown in: Share a column between tables (Presets), Switch grouping off (Grouping)

ValuePaths<W>

NameTypeDefaultDescription
sortValue(value: Present<W>) => string | number | Absent—What is sorted by, when not by the value itself.

Shown in: Share a column between tables (Presets), Sort and export a value that is an object (Sorting), Work through the alerts (Scenarios)

exportValue(value: Present<W>) => string | number | Absent—What stands in the export, when not the value itself.

Shown in: Share a column between tables (Presets), Sort and export a value that is an object (Sorting)

groupValue(value: Present<W>) => string | number | boolean | Date | Absent—What is grouped by, when not the value itself - a group header of a number, say. Rows whose group value is absent form a group of their own.

Shown in: Share a column between tables (Presets), Group numbers in bands (Grouping)

Types on this page

FormatFor<W>

What a column accepts as format: a number format for numbers, a date format for points in time, nothing for every other value.

type FormatFor<W> = [Present<W>] extends [number]
  ? NumberFormat
  : [Present<W>] extends [Date]
    ? DateFormat
    : never;

FilterFor<W>

What a column accepts as filter: "list" for every value with a text form, "range" for numbers and points in time, a filter from columnFilter only where its value type fits.

type FilterFor<W> =
  | (IsDisplayable<W> extends true ? "list" : never)
  | ([Present<W>] extends [number] ? "range" : [Present<W>] extends [Date] ? "range" : never)
  // eslint-disable-next-line @typescript-eslint/no-explicit-any -- the condition belongs to the filter; at the column only the value counts
  | ColumnFilter<Present<W>, any>;

GroupFor<W>

group on a point in time: its rows are grouped by day, ISO week, month or year.

type GroupFor<W> = [Present<W>] extends [Date] ? DatePeriod : never;

EditFor<W, Z>

What a column accepts as edit: the core field for its value - "text" for text, "number" for numbers, "date" for points in time, "select" for text and numbers - or an editor of one's own.

type EditFor<W, Z> =
  | ([Present<W>] extends [string] ? "text" | "select" : never)
  | ([Present<W>] extends [number] ? "number" | "select" : never)
  | ([Present<W>] extends [Date] ? "date" : never)
  | ((editor: CellEditorProps<W, Z>) => ReactNode);

Present<W>

The value without the absence - that is what children gets.

type Present<W> = Exclude<W, Absent>;

Presentation<W, Z>

What a column draws in a cell: the value present and its row.

type Presentation<W, Z> = (value: Present<W>, row: Z) => ReactNode;

Pin

Where a pinned column stands: in the block before or after the others.

type Pin = "start" | "end";

Absent

A value that is not there: null or undefined.

type Absent = null | undefined;

NumberFormat

The names of the standard presentations of a number.

type NumberFormat = "percent" | "count" | { decimals: number };

DateFormat

The names of the standard presentations of a point in time.

type DateFormat = "date" | "time" | "dateTime";

IsDisplayable<W>

true where the value present can appear as text without children (Displayable), false otherwise - what decides the filters and aggregates a column offers.

type IsDisplayable<W> = [Present<W>] extends [never]
  ? false
  : [Present<W>] extends [Displayable]
    ? true
    : false;

DatePeriod

The span a point in time is grouped by.

type DatePeriod = "day" | "week" | "month" | "year";

CellEditorProps<W, Z>

What an editor of one's own receives. The table holds the draft; Enter, Escape and Tab stay the table's, bubbling up from the field.

NameTypeDefaultDescription
valuerequiredW | null—The draft - the cell's value until the field changes it.
commitrequired(value?: W | null) => void—Commits - the draft, or the value given. A picker commits on its pick.
rowrequiredZ—
invalidrequiredboolean—The draft did not validate: the field shows it; the message stands beneath.
labelrequiredstring—The field's name: what is edited, for which row. The FormField around the editor already names a core field by it.

Events

NameTypeDefaultDescription
onChangerequired(value: W | null) => void—Changes the draft.

Displayable

What can appear as text without children.

type Displayable = string | number | boolean | Date;

Every page of @umriss-ui/table

Getting started

Columns

Finding rows

Grouping

Rows

Around the table

Many rows

Editing

Limits and alarms

API index