First table

Rows of records to scan, sort, search and select (also called a data table or data grid). Reach for it when people compare many records by the same fields; from a few rows to a million on a server.

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

useTable takes the rows and hands back the Table and the Column that belong to them, so a column can only name a field the row has. The parts that touch no row, such as Toolbar or Search, are ordinary imports.

Every option of the hook and every prop of the table stands in the tables below; each feature has a page of its own in the sidebar.

Examples

Show a list of services

Pass the rows to useTable and declare a Column per field; rowHeader names the row, and numbers align right and sort by value.

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

interface Service {
  id: string;
  name: string;
  team: string;
  tier: number;
  latencySlo: number;
}

const SERVICES: Service[] = [
  { id: "checkout", name: "Checkout", team: "Payments", tier: 1, latencySlo: 300 },
  { id: "sign-in", name: "Sign-in", team: "Identity", tier: 1, latencySlo: 200 },
  { id: "search", name: "Search", team: "Discovery", tier: 1, latencySlo: 250 },
  { id: "notifications", name: "Notifications", team: "Messaging", tier: 2, latencySlo: 800 },
];

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

  return (
    <Table ariaLabel="Services">
      <Column value="name" label="Service" rowHeader />
      <Column value="team" label="Team" />
      <Column value="tier" label="Tier" />
      <Column value="latencySlo" label="Latency objective (ms)" />
    </Table>
  );
}

Search and page from a header of your own

t.search and t.setSearch bind a field of the application's own, t.page and t.setPage a pager in the card's header. The table pages only while a Pagination stands in it; both pagers move the same page.

import { Button, Card, CardBody, CardHeader, Input, Stack, Text } from "@umriss-ui/core";
import { Pagination, useTable } from "@umriss-ui/table";
import type { TableRef } from "@umriss-ui/table";

interface Service {
  id: string;
  name: string;
  team: string;
  tier: number;
}

const SERVICES: Service[] = [
  { id: "checkout", name: "Checkout", team: "Payments", tier: 1 },
  { id: "billing", name: "Billing", team: "Payments", tier: 1 },
  { id: "refunds", name: "Refunds", team: "Payments", tier: 2 },
  { id: "sign-in", name: "Sign-in", team: "Identity", tier: 1 },
  { id: "accounts", name: "Accounts", team: "Identity", tier: 2 },
  { id: "search", name: "Search", team: "Discovery", tier: 1 },
  { id: "recommendations", name: "Recommendations", team: "Discovery", tier: 3 },
  { id: "catalogue", name: "Catalogue", team: "Discovery", tier: 2 },
  { id: "notifications", name: "Notifications", team: "Messaging", tier: 2 },
  { id: "webhooks", name: "Webhooks", team: "Messaging", tier: 3 },
  { id: "image-service", name: "Image service", team: "Platform", tier: 2 },
  { id: "reporting", name: "Reporting", team: "Platform", tier: 3 },
];

/* Written once for every table of the application, so it takes the table as
   `of`, as the package's own parts do. It says which rows stand on the page,
   out of how many: `rowCount` is what the search leaves, `rows` all there are.
   A table that scrolls instead of paging has no page to step through. */
function RangePager({ of }: { of: TableRef }) {
  if (of.virtual || of.rowCount === 0) return null;
  const first = (of.page - 1) * of.pageSize + 1;
  const last = Math.min(of.page * of.pageSize, of.rowCount);
  return (
    <Stack direction="row" gap={1} align="center">
      <Text size="sm" tone="secondary">
        {first}–{last} of {of.rowCount}
        {of.rowCount < of.rows.length && ` (${of.rows.length} in all)`}
      </Text>
      <Button size="sm" aria-label="Previous page" disabled={of.page <= 1} onClick={() => of.setPage(of.page - 1)}>
        ‹
      </Button>
      <Button size="sm" aria-label="Next page" disabled={of.page >= of.pageCount} onClick={() => of.setPage(of.page + 1)}>
        ›
      </Button>
    </Stack>
  );
}

export default function SearchAndPageFromAHeader() {
  const t = useTable(SERVICES, { rowKey: (s) => s.id, pageSize: 5 });
  const { Table, Column } = t;
  const all = t.pageSize >= t.rows.length;

  return (
    <Card>
      <CardHeader
        divider
        title="Services"
        actions={
          <Stack direction="row" gap={2} align="center" wrap>
            <Input
              type="search"
              size="sm"
              chars={18}
              placeholder="Service or team"
              aria-label="Search the services"
              value={t.search}
              onChange={(event) => t.setSearch(event.target.value)}
            />
            <RangePager of={t} />
            <Button size="sm" onClick={() => t.setPageSize(all ? 5 : t.rows.length)}>
              {all ? "Five per page" : "Show all"}
            </Button>
          </Stack>
        }
      />
      <CardBody>
        <Table ariaLabel="Services">
          <Column value="name" label="Service" rowHeader />
          <Column value="team" label="Team" />
          <Column value="tier" label="Tier" />
          <Pagination pageSizes={[5, 10]} />
        </Table>
      </CardBody>
    </Card>
  );
}

Sort, arrange and export from controls of your own

t.toggleSort, t.toggleColumn and t.setOrder change what the headers and the column menu change; t.sort, t.hidden and t.order say how it stands. t.asCsv() is the text an export writes.

import { useState } from "react";
import { Button, Checkbox, Select, Stack } from "@umriss-ui/core";
import { useTable } from "@umriss-ui/table";
import type { TableRef } from "@umriss-ui/table";

interface Service {
  id: string;
  name: string;
  team: string;
  tier: number;
  latencySlo: number;
}

const SERVICES: Service[] = [
  { id: "checkout", name: "Checkout", team: "Payments", tier: 1, latencySlo: 300 },
  { id: "sign-in", name: "Sign-in", team: "Identity", tier: 1, latencySlo: 200 },
  { id: "search", name: "Search", team: "Discovery", tier: 1, latencySlo: 250 },
  { id: "notifications", name: "Notifications", team: "Messaging", tier: 2, latencySlo: 800 },
  { id: "reporting", name: "Reporting", team: "Platform", tier: 3, latencySlo: 2000 },
];

/** The columns the panel arranges, by id - the row header stays where it is. */
const COLUMNS = [
  { id: "team", label: "Team" },
  { id: "tier", label: "Tier" },
  { id: "latencySlo", label: "Latency objective (ms)" },
] as const;

/* Written once for every table of the application. In server mode the table
   holds one page of the server's, and that is all `asCsv` can write - so the
   button says so instead of pretending to copy the whole list. */
function CopyAsCsv({ of }: { of: TableRef }) {
  const [copied, setCopied] = useState(false);
  return (
    <Button
      size="sm"
      onClick={() => {
        void navigator.clipboard.writeText(of.asCsv()).then(() => setCopied(true));
      }}
    >
      {copied ? "Copied" : of.server ? "Copy this page as CSV" : "Copy as CSV"}
    </Button>
  );
}

export default function SortArrangeAndExport() {
  const t = useTable(SERVICES, { rowKey: (s) => s.id, defaultSort: { column: "tier", direction: "asc" } });
  const { Table, Column } = t;
  /* Empty until somebody arranges: then the natural order. */
  const order = t.order.length > 0 ? t.order.filter((id) => COLUMNS.some((c) => c.id === id)) : COLUMNS.map((c) => c.id);
  const sorted = t.sort[0];

  const moveLeft = (id: string) => {
    const at = order.indexOf(id);
    t.setOrder(["name", ...order.slice(0, at - 1), id, order[at - 1]!, ...order.slice(at + 1)]);
  };

  return (
    <Stack gap={3}>
      <Stack direction="row" gap={2} align="center" wrap>
        <Select size="sm" aria-label="Sort by" value={sorted?.column ?? ""} onChange={(event) => t.toggleSort(event.target.value)}>
          <option value="name">Service</option>
          {COLUMNS.map((c) => (
            <option key={c.id} value={c.id}>
              {c.label}
            </option>
          ))}
        </Select>
        <Button size="sm" variant="ghost" onClick={() => sorted && t.toggleSort(sorted.column)}>
          {sorted?.direction === "desc" ? "Descending" : "Ascending"}
        </Button>
        <CopyAsCsv of={t} />
      </Stack>
      <Stack direction="row" gap={3} align="center" wrap>
        {order.map((id, i) => {
          const label = COLUMNS.find((c) => c.id === id)!.label;
          return (
            <Stack key={id} direction="row" gap={1} align="center">
              <Button size="sm" variant="ghost" aria-label={`Move ${label} to the left`} disabled={i === 0} onClick={() => moveLeft(id)}>
                ←
              </Button>
              <Checkbox label={label} checked={!t.hidden.includes(id)} onChange={() => t.toggleColumn(id)} />
            </Stack>
          );
        })}
      </Stack>
      <Table ariaLabel="Services">
        <Column value="name" label="Service" rowHeader />
        <Column value="team" label="Team" />
        <Column value="tier" label="Tier" />
        <Column value="latencySlo" label="Latency objective (ms)" />
      </Table>
    </Stack>
  );
}

When to use something else

Keyboard

Key Action
Enter / Space On a sortable header: sorts by the column, ascending; pressed again, descending.
Escape Closes the tip that shows a cut value whole.

Accessibility

A native table, named by ariaLabel: without it a screen reader announces a table with no name, so pass one, the more so where a page holds two. The rowHeader column's cells are row headers, so a reader names the row as it moves along it, and the row's checkbox, expander and actions are named after it too.

A sortable header holds a button, and its cell carries aria-sort, so the reader says which column orders the rows and in which direction. While loading is set the table is aria-busy.

A value cut short at the edge of its cell shows whole in a tip, under the pointer and on keyboard focus alike; the cell's text is the whole value all along, so a reader never meets the cut.

API

TableProps<Z>

NameTypeDefaultDescription
selectableboolean—Adds the selection column. "All" means: the filtered set.

Shown in: Name the row (Column), Pin columns to both sides (Width and pinning), Keep the header and the names in view (Width and pinning) and 14 more

stickyHeaderboolean—Keeps the header row visible while scrolling.

Shown in: Keep the header and the names in view (Width and pinning), Fold groups into a summary (Grouping), Group twenty thousand rows (Grouping) and 4 more

stickyRowHeaderboolean—Keeps the row header column visible while scrolling sideways - the same as pin="start" on the row header.

Shown in: Keep the header and the names in view (Width and pinning), Stripes across a wide table (Row appearance), Show, hide and arrange (ColumnMenu) and 1 more

stripedboolean—Discreet zebra stripes.

Shown in: Stripes across a wide table (Row appearance)

density"regular" | "compact"the density of the UmrissProvider, else "regular"How tightly the rows stand.

Shown in: Fit long group names in a compact table (Grouping), Employees in the detail (Tree rows), Own staff and teams (Tree rows) and 4 more

maxHeightstring—Height of the scroll area, e.g. "480px". A virtualised table needs it.

Shown in: Keep the header and the names in view (Width and pinning), Fold groups into a summary (Grouping), Group twenty thousand rows (Grouping) and 4 more

emptyReactNode—What stands in the body when there are no rows. Empty because of a filter the table shows itself – with the way back.

Shown in: Restrict which rows a table has (Pre-filter), Show loading, empty, failed and no match (Row appearance)

loadingboolean—Shows placeholder rows instead of the rows.

Shown in: Rows from a request (Filter), Show loading, empty, failed and no match (Row appearance), Show loading, and drop a late answer (Server mode) and 1 more

rowProps(row: Z) => RowAttributes—Classes and data attributes per row.

Shown in: Mark rows by their state (Row appearance)

groupableboolean—false: the user cannot group this table - the column menu offers no grouping, and a grouping handed in is passed over.

Shown in: Switch grouping off (Grouping)

gridboolean—Grid mode: the table is one tab stop, and the arrows walk its cells - Home and End in the row, Ctrl+Home and Ctrl+End to the table's ends, PageUp and PageDown by the rows in view. A cell's own controls are reached with Enter or F2 and left with Escape; a column with edit is edited in place. Without it the table stays a native table.

Shown in: Work cell by cell (Grid mode), Combine grid mode with groups, pinning and many rows (Grid mode), Edit a comments column (Edits) and 6 more

editMode"cell" | "row"—Grid mode (ADR-0036): "cell" reports every cell as it is left (onCellEdit); "row" opens every cell of a row that edits at once, as one Row draft, and reports it only when the row is saved on purpose (onRowSave) - by its Save button or Enter; Escape or Discard drops it. A row with a draft is left only by saving or discarding it.

Shown in: Save a row on purpose (Edits), Check a whole row before it is saved (Edits), Keep a list: add, change and delete rows (Edits) and 1 more

newRow() => Partial<Z>—What a new row starts with - the values the columns read before anything is typed. Without it, nothing.

Shown in: Keep a list: add, change and delete rows (Edits)

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

Shown in: Show a list of services, Search and page from a header of your own, Sort, arrange and export from controls of your own and 118 more

Events · 4
NameTypeDefaultDescription
onCellEdit(edit: CellEdit<Z>) => void—Grid mode: an edit the user committed. The table applies nothing; the cell shows the new value when the rows passed in carry it.

Shown in: Combine grid mode with groups, pinning and many rows (Grid mode), Edit a comments column (Edits), Check an edit before it is reported (Edits) and 1 more

onRowSave(save: RowSave<Z>) => void—editMode="row": a saved Row draft, with the columns it changed.

Shown in: Save a row on purpose (Edits), Check a whole row before it is saved (Edits), Keep a list: add, change and delete rows (Edits) and 1 more

onRowAdd(add: RowAdd) => void—Grid mode: puts up a "New row" button - in the table toolbar, or beneath the table without one. It opens an empty Row draft above the rows; saved, it is reported whole. The row appears when the rows passed in carry it.

Shown in: Keep a list: add, change and delete rows (Edits)

onRowDelete(remove: RowDelete<Z>) => void—Grid mode: a Delete button on every row, which asks inside its row before the delete is reported. The table removes nothing.

Shown in: Keep a list: add, change and delete rows (Edits), Edit a dense table (Edits)

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

Shown in: Show a list of services, Search and page from a header of your own, Sort, arrange and export from controls of your own and 118 more

Styling · 1

TableOptions<Z>

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

Shown in: Show a list of services, Search and page from a header of your own, Sort, arrange and export from controls of your own and 118 more

pageSizenumber10How many rows a page holds.

Shown in: Search and page from a header of your own, Ask a question over several fields (Filter), Restrict which rows a table has (Pre-filter) and 7 more

defaultSort| SortLevel
| readonly SortLevel[]
| null
nullThe application's sort; it is the default that view leaves out.

Shown in: Sort, arrange and export from controls of your own, A label wider than its column (Width and pinning), Sort by several columns (Sorting) and 10 more

defaultGroupingGroupingId<Z> | readonly GroupingId<Z>[]ungrouped, until the user groupsThe application's grouping: a column or group key, or up to three, the outermost first. It is the default that view leaves out.

Shown in: Group by one column (Grouping), Group on two levels (Grouping), Group on three levels (Grouping) and 23 more

preFilter(row: Z) => boolean—Which rows the table has at all – by permission, by tenant, by anything the user is not meant to undo. It is invisible: never a condition, never reset, never part of the view, and "43 of 1,204" counts only the rows it admits. It may stand in the call; a change leaves the page standing, and the model clamps it.

Shown in: Restrict which rows a table has (Pre-filter)

rowFiltersreadonly RowFilter<Z, any>[]—The row filters the table has: conditions the application defines over the whole row and sets with t.setFilter(filter, condition) from a control of its own. Named here so that a condition from initialView, which arrives by id, is known in the first render.

Shown in: Filter by a field no column shows (Filter), Rows from a request (Filter), Ask a question over several fields (Filter) and 3 more

initialViewTableView—The view to start from, read once when the hook first runs; a different value on a later render is ignored, as React ignores a changed defaultValue. To go to a view later, call setView. The table remembers none: where a view is kept is the application's decision. Names no column carries fall out.

Shown in: Ask a question over several fields (Filter), Fold groups into a summary (Grouping), Show loading, empty, failed and no match (Row appearance) and 2 more

virtualboolean | { overscan?: number }—Renders only what stands in the scroll area; paging is then off. Every row is one row pitch tall, which the table measures itself (ADR-0042).

Shown in: Group twenty thousand rows (Grouping), Scroll twenty thousand rows (Virtualisation), Measure two hundred thousand rows (Benchmark) and 1 more

selectionTableSelection<string>—A selection from outside instead of its own – when the application holds it.

Shown in: Hold the selection yourself (Selection)

Events

NameTypeDefaultDescription
onViewChange(view: TableView) => void—Every change the reader makes, once, always the whole view - the one to keep, or to hand another table's setView. Not called for the view the table starts with, nor for one setView applies.

Shown in: Keep and restore a view (View)

TableSnapshot<Z>

NameTypeDefaultDescription
rowsrequiredreadonly Z[]—All the rows passed in, including those the pre-filter does not admit.

Shown in: Search and page from a header of your own

filteredrequiredreadonly Z[]—The filtered set: what the pre-filter admits and search and filters leave, across all pages.

Shown in: Select rows (Selection), Scroll twenty thousand rows (Virtualisation)

visiblerequiredreadonly Z[]—What is rendered right now: the page or the window.

Shown in: Scroll twenty thousand rows (Virtualisation)

pagerequirednumber—The page shown, from 1. Without Pagination always 1.

Shown in: Search and page from a header of your own

pageCountrequirednumber—How many pages the filtered set makes.

Shown in: Search and page from a header of your own

pageSizerequirednumber—Rows per page.

Shown in: Search and page from a header of your own

setPagerequired(page: number) => void—Changes the page, clamped to those that exist.

Shown in: Search and page from a header of your own

setPageSizerequired(size: number) => void—Sets the rows per page; back to page one.

Shown in: Search and page from a header of your own

setSearchrequired(search: string) => void—Sets the search text; back to page one.

Shown in: Search and page from a header of your own

sortrequiredreadonly SortLevel[]—The sort levels, the first one first – only over columns that registered.

Shown in: Sort, arrange and export from controls of your own

toggleSortrequired(column: string, additive?: boolean) => void—Sorts by a column. With additive a level is added instead of replacing the sort.

Shown in: Sort, arrange and export from controls of your own

hiddenrequiredreadonly string[]—The ids of the hidden columns.

Shown in: Sort, arrange and export from controls of your own

toggleColumnrequired(column: string) => void—Hides a column or shows it again. The row header stays.

Shown in: Sort, arrange and export from controls of your own

orderrequiredreadonly string[]—The order of the columns as ids, as the user chose it.

Shown in: Sort, arrange and export from controls of your own

setOrderrequired(order: readonly string[]) => void—Reorders the columns – on header, body and footer at once.

Shown in: Sort, arrange and export from controls of your own

pinnedrequiredReadonly<Record<string, Pin>>—The pinned columns by id - the declared ones until the user chooses.

Shown in: Pin and size from controls of your own (Width and pinning)

setPinrequired(column: string, pin: Pin | null) => void—Pins a column to the start or the end, or unpins it with null.

Shown in: Pin and size from controls of your own (Width and pinning)

widthsrequiredReadonly<Record<string, number>>—The widths in pixels: those of the columns, and over them the dragged ones.

Shown in: Pin and size from controls of your own (Width and pinning)

setWidthrequired(column: string, width: number | undefined) => void—Sets the width of a column; undefined takes it back.

Shown in: Pin and size from controls of your own (Width and pinning)

expandedrequiredreadonly string[]—The keys of the expanded rows.

Shown in: Open a detail from outside the table (RowDetail)

toggleRowrequired(key: string) => void—Expands a row or collapses it.

Shown in: Open a detail from outside the table (RowDetail)

filterrequiredReadonly<Record<string, unknown>>—The conditions of the column filters and row filters by id, in the order in which they were set. Whatever fits no column and no row filter is absent.

Shown in: Set a condition from outside the table (Filter)

setFilterrequiredSetFilter<Z>—Sets the condition of a column filter or a row filter; null clears it. Back to page one. A condition that does not fit the kind of the filter – a range on a list filter, a column without a filter, a row filter not in rowFilters – is passed over with a warning in development.

Shown in: Set a condition from outside the table (Filter), Filter by a field no column shows (Filter), Rows from a request (Filter) and 5 more

conditionOfrequired<B>(filter: RowFilter<Z, B>) => B | null—The condition of a row filter, as its type - null while it is lifted.

Shown in: Filter by a field no column shows (Filter), Rows from a request (Filter), Ask a question over several fields (Filter) and 3 more

selectionrequiredTableSelection<string>—The selection – its own or the one handed in through selection.

Shown in: Select rows (Selection), Copy the selection (Export), Scroll twenty thousand rows (Virtualisation) and 3 more

groupingrequiredreadonly string[]—The ids the table is grouped by, the outermost first – only those a groupable column or a group key carries, at most three.

Shown in: Group and fold from controls of your own (Grouping)

setGroupingrequired(grouping: readonly string[]) => void—Groups by these columns or group keys; [] ungroups. Back to page one.

Shown in: Group and fold from controls of your own (Grouping)

setViewrequired(view: TableView) => void—Goes to a whole view; what it leaves out goes back to where the table starts, and names no column carries fall out. Not reported through onViewChange: two tables kept in step by onViewChange: (v) => other.setView(v) in both directions do not answer each other. A view with the content already shown changes nothing.

Shown in: Keep and restore a view (View)

foldedrequiredreadonly string[]—The paths of the folded groups that still occur.

Shown in: Group and fold from controls of your own (Grouping)

toggleFoldrequired(path: string) => void—Folds a group or unfolds it. A group of one row does not fold.

Shown in: Group and fold from controls of your own (Grouping)

foldAllrequired() => void—Folds every group that has more than one row.

Shown in: Fold groups into a summary (Grouping)

unfoldAllrequired() => void—Unfolds every group.

Shown in: Fold groups into a summary (Grouping)

branchesrequiredreadonly string[]—Tree rows: the keys of the open branches.

Shown in: Open branches from controls of your own (Tree rows)

toggleBranchrequired(key: string) => void—Tree rows: opens a branch or closes it.

Shown in: Open branches from controls of your own (Tree rows)

unfoldAllBranchesrequired() => void—Tree rows: opens every branch that has children.

Shown in: Open branches from controls of your own (Tree rows)

foldAllBranchesrequired() => void—Tree rows: closes every branch.

Shown in: Open branches from controls of your own (Tree rows)

viewrequiredTableView—The part of the state an application can keep; whatever still stands as the options set it up is absent.

Shown in: Set a condition from outside the table (Filter), Fold groups into a summary (Grouping), Keep and restore a view (View)

asCsvrequired() => string—The filtered set in the visible columns as text for a spreadsheet - in server mode the page.

Shown in: Sort, arrange and export from controls of your own

selectionAsTextrequired() => string—The selected rows within the filtered set as tab-separated text under a header line - the text Ctrl+C writes. "" with nothing selected.

Shown in: Copy the selection (Export)

virtualrequiredboolean—Whether it virtualises.

Shown in: Search and page from a header of your own

rowCountrequirednumber—How many rows the filtered set has - in server mode the server's count.

Shown in: Search and page from a header of your own

serverrequiredboolean—Whether it is in server mode: the rows are one page of a server's.

Shown in: Sort, arrange and export from controls of your own

Types on this page

RowAttributes

What a row carries in classes and data attributes.

type RowAttributes = { className?: string } & {
  [data: `data-${string}`]: string | undefined;
};

RowSave<Z>

A Row draft the user saved (ADR-0036) - reported, not applied.

NameTypeDefaultDescription
rowKeyrequiredstring—

Shown in: Save a row on purpose (Edits), Check a whole row before it is saved (Edits), Keep a list: add, change and delete rows (Edits) and 1 more

changesrequiredReadonly<Record<string, unknown>>—The columns the draft changed, by column id, with their editors' values.

Shown in: Save a row on purpose (Edits), Check a whole row before it is saved (Edits), Keep a list: add, change and delete rows (Edits) and 1 more

rowrequiredZ—

Shown in: Save a row on purpose (Edits)

RowAdd

A new row the user saved: every column that edits, by column id.

NameTypeDefaultDescription
valuesrequiredReadonly<Record<string, unknown>>—

Shown in: Keep a list: add, change and delete rows (Edits)

RowDelete<Z>

A row the user asked to delete, and confirmed.

NameTypeDefaultDescription
rowKeyrequiredstring—

Shown in: Keep a list: add, change and delete rows (Edits), Edit a dense table (Edits)

rowrequiredZ—

SortLevel<K extends string = string>

One level of the sort. Several levels act in their order: the second decides only what the first leaves open.

GroupingId<Z>

A column or group key the table can be grouped by. The columns stand in the JSX, which the hook does not see: a field name is suggested, any id is taken, and one that nothing carries falls out.

type GroupingId<Z> = Field<Z> | (string & {});

TableView<K extends string = string>

Search, conditions, sort levels, page, page size, hidden columns, order, dragged widths, the grouping and the pinned columns. Whatever is at its default is absent. The pre-filter never belongs to it: it is a function of the application.

NameTypeDefaultDescription
conditionsReadonly<Record<string, unknown>>—The conditions of the column filters, per column. A condition for a column that does not exist, or whose filter is of another kind, falls out.

Shown in: Ask a question over several fields (Filter), A dispatcher's bar (Toolbar controls), Hand the table one page (Server mode) and 4 more

sortreadonly SortLevel<K>[]—

Shown in: Keep and restore a view (View), Hand the table one page (Server mode), Sort and search on the server (Server mode) and 3 more

pagenumber—

Shown in: Hand the table one page (Server mode), Sort and search on the server (Server mode), Name the values a list filter offers (Server mode) and 2 more

pageSizenumber—

Shown in: Hand the table one page (Server mode), Sort and search on the server (Server mode), Name the values a list filter offers (Server mode) and 2 more

hiddenreadonly K[]—

Shown in: Keep and restore a view (View)

orderreadonly K[]—
widthsReadonly<Record<string, number>>—Column widths the user has dragged, in pixels.
groupingreadonly K[]—The columns and group keys the table is grouped by, the outermost first.
foldedreadonly string[]—The paths of the folded groups. A path that no longer occurs falls out.

Shown in: Fold groups into a summary (Grouping)

branchesreadonly string[]—Tree rows: the keys of the open branches. A key that no longer occurs falls out.
pinnedReadonly<Record<string, Pin>>—The pinned columns, whole, once the user's choice deviates from what the columns declare - {} when every declared pin was undone. A column that does not exist falls out.

TableSelection<T extends string | number>

What useTableSelection returns: the selected keys and the gestures on them. Give it to useTable as selection when the application holds the selection.

NameTypeDefaultDescription
selectedrequiredReadonlySet<T>—The keys currently selected.

Shown in: Work through the alerts (Scenarios)

isSelectedrequired(id: T) => boolean—

Shown in: Select rows (Selection), Hold the selection yourself (Selection)

togglerequired(id: T) => void—

Shown in: Hold the selection yourself (Selection)

toggleAllrequired() => void—Selects all the keys passed in - or, when all of them are selected already, deselects them. Keys outside them stay as they are.
clearrequired() => void—

Shown in: Hold the selection yourself (Selection), Work through the alerts (Scenarios), Find a late shipment (Scenarios)

allSelectedrequiredboolean—true when every key passed in is selected.
someSelectedrequiredboolean—true when some - but not all - are selected.
countrequirednumber—

Shown in: Select rows (Selection), Hold the selection yourself (Selection), Copy the selection (Export) and 2 more

Pin

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

type Pin = "start" | "end";

SetFilter<Z>

Sets a condition from outside. Typed at the field of the row, not at the column: the columns stand in the JSX, and the hook does not see that (ADR-0017). An id that is not a field name – a computed column or an id of one's own – takes any condition; through it the condition of a filter of one's own is set as well.

interface SetFilter<Z> {
  <B>(filter: RowFilter<Z, B>, condition: B | null): void;
  <K extends Field<Z>>(column: K, condition: FieldCondition<Z[K]> | null): void;
  <I extends string>(column: I & (I extends Field<Z> ? never : unknown), condition: unknown): void;
}

SortDirection

The direction of one sort level: asc puts the smallest value on top, desc the largest.

type SortDirection = "asc" | "desc";

Field<Z>

The field names of a row.

type Field<Z> = Extract<keyof Z, string>;

FieldCondition<W>

What can be set as a condition on a field: a list of its values, and for numbers and points in time a range as well.

type FieldCondition<W> =
  | ListCondition<W>
  | ([Present<W>] extends [number] ? RangeCondition<number> : [Present<W>] extends [Date] ? RangeCondition<Date> : never);

ListCondition<W>

The condition of a list filter: the chosen values, an absent one as null.

type ListCondition<W> = readonly (Present<W> | null)[];

Present<W>

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

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

RangeCondition<W>

The condition of a range filter: both bounds inclusive, each may be absent.

NameTypeDefaultDescription
fromW—
toW—

Absent

A value that is not there: null or undefined.

type Absent = null | undefined;

Known limits

What umriss deliberately does not build, and why: ADR-0032.

Every page of @umriss-ui/table

Getting started

Columns

Finding rows

Grouping

Rows

Around the table

Many rows

Editing

Limits and alarms

API index