# First table

> Package `@umriss-ui/table`, version 0.40.0. Demo page: <https://romanhaendler.github.io/umriss-ui/table/first-table/>. 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).

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.

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

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

[`useTable`](https://romanhaendler.github.io/umriss-ui/table/api/#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`](https://romanhaendler.github.io/umriss-ui/table/api/#useTable) and declare a `Column` per field; `rowHeader` names the row, and numbers align right and sort by value.

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

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

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

- Measured values read against their limits → VerdictColumn
- Three or four key figures side by side → `Stat` from @umriss-ui/core

## 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>`

| Prop | Type | Default | Description |
|---|---|---|---|
| `selectable` | `boolean` | — | Adds the selection column. "All" means: the filtered set.<br>Shown in: [Name the row (Column)](https://romanhaendler.github.io/umriss-ui/table/column/#row-header), [Pin columns to both sides (Width and pinning)](https://romanhaendler.github.io/umriss-ui/table/width-and-pinning/#pinned-both-sides), [Keep the header and the names in view (Width and pinning)](https://romanhaendler.github.io/umriss-ui/table/width-and-pinning/#sticky-parts) and 14 more |
| `stickyHeader` | `boolean` | — | Keeps the header row visible while scrolling.<br>Shown in: [Keep the header and the names in view (Width and pinning)](https://romanhaendler.github.io/umriss-ui/table/width-and-pinning/#sticky-parts), [Fold groups into a summary (Grouping)](https://romanhaendler.github.io/umriss-ui/table/grouping/#the-overview), [Group twenty thousand rows (Grouping)](https://romanhaendler.github.io/umriss-ui/table/grouping/#twenty-thousand-rows) and 4 more |
| `stickyRowHeader` | `boolean` | — | Keeps the row header column visible while scrolling sideways - the same as `pin="start"` on the row header.<br>Shown in: [Keep the header and the names in view (Width and pinning)](https://romanhaendler.github.io/umriss-ui/table/width-and-pinning/#sticky-parts), [Stripes across a wide table (Row appearance)](https://romanhaendler.github.io/umriss-ui/table/row-appearance/#stripes-across-a-wide-table), [Show, hide and arrange (ColumnMenu)](https://romanhaendler.github.io/umriss-ui/table/columnmenu/#show-hide-and-arrange) and 1 more |
| `striped` | `boolean` | — | Discreet zebra stripes.<br>Shown in: [Stripes across a wide table (Row appearance)](https://romanhaendler.github.io/umriss-ui/table/row-appearance/#stripes-across-a-wide-table) |
| `density` | `"regular" \| "compact"` | the density of the `UmrissProvider`, else `"regular"` | How tightly the rows stand.<br>Shown in: [Fit long group names in a compact table (Grouping)](https://romanhaendler.github.io/umriss-ui/table/grouping/#compact-and-long-values), [Employees in the detail (Tree rows)](https://romanhaendler.github.io/umriss-ui/table/tree-rows/#employees-in-the-detail), [Own staff and teams (Tree rows)](https://romanhaendler.github.io/umriss-ui/table/tree-rows/#own-staff-and-teams) and 4 more |
| `maxHeight` | `string` | — | Height of the scroll area, e.g. "480px". A virtualised table needs it.<br>Shown in: [Keep the header and the names in view (Width and pinning)](https://romanhaendler.github.io/umriss-ui/table/width-and-pinning/#sticky-parts), [Fold groups into a summary (Grouping)](https://romanhaendler.github.io/umriss-ui/table/grouping/#the-overview), [Group twenty thousand rows (Grouping)](https://romanhaendler.github.io/umriss-ui/table/grouping/#twenty-thousand-rows) and 4 more |
| `empty` | `ReactNode` | — | What stands in the body when there are no rows. Empty because of a filter the table shows itself – with the way back.<br>Shown in: [Restrict which rows a table has (Pre-filter)](https://romanhaendler.github.io/umriss-ui/table/pre-filter/#pre-filter), [Show loading, empty, failed and no match (Row appearance)](https://romanhaendler.github.io/umriss-ui/table/row-appearance/#states) |
| `loading` | `boolean` | — | Shows placeholder rows instead of the rows.<br>Shown in: [Rows from a request (Filter)](https://romanhaendler.github.io/umriss-ui/table/filter/#rows-from-a-request), [Show loading, empty, failed and no match (Row appearance)](https://romanhaendler.github.io/umriss-ui/table/row-appearance/#states), [Show loading, and drop a late answer (Server mode)](https://romanhaendler.github.io/umriss-ui/table/server-mode/#loading-and-late-answers) and 1 more |
| `rowProps` | `(row: Z) => `[`RowAttributes`](https://romanhaendler.github.io/umriss-ui/table/first-table/#type-RowAttributes) | — | Classes and data attributes per row.<br>Shown in: [Mark rows by their state (Row appearance)](https://romanhaendler.github.io/umriss-ui/table/row-appearance/#styling-rows) |
| `groupable` | `boolean` | — | `false`: the user cannot group this table - the column menu offers no grouping, and a grouping handed in is passed over.<br>Shown in: [Switch grouping off (Grouping)](https://romanhaendler.github.io/umriss-ui/table/grouping/#switched-off) |
| `grid` | `boolean` | — | 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.<br>Shown in: [Work cell by cell (Grid mode)](https://romanhaendler.github.io/umriss-ui/table/grid-mode/#grid-mode), [Combine grid mode with groups, pinning and many rows (Grid mode)](https://romanhaendler.github.io/umriss-ui/table/grid-mode/#the-whole-grid), [Edit a comments column (Edits)](https://romanhaendler.github.io/umriss-ui/table/edits/#comments-column) and 6 more |
| `editMode` | `"cell" \| "row"` | — | Grid mode ([ADR-0036](https://github.com/romanhaendler/umriss-ui/blob/main/docs/adr/0036-a-grid-edits-on-the-first-click-and-saves-rows-on-purpose.md)): `"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.<br>Shown in: [Save a row on purpose (Edits)](https://romanhaendler.github.io/umriss-ui/table/edits/#save-a-row-on-purpose), [Check a whole row before it is saved (Edits)](https://romanhaendler.github.io/umriss-ui/table/edits/#check-a-whole-row), [Keep a list: add, change and delete rows (Edits)](https://romanhaendler.github.io/umriss-ui/table/edits/#keep-a-list) and 1 more |
| `newRow` | `() => Partial<Z>` | — | What a new row starts with - the values the columns read before anything is typed. Without it, nothing.<br>Shown in: [Keep a list: add, change and delete rows (Edits)](https://romanhaendler.github.io/umriss-ui/table/edits/#keep-a-list) |
| `children` | `ReactNode` | — | Columns, `RowDetail`, `RowActions` and the unbound parts.<br>Shown in: [Show a list of services](https://romanhaendler.github.io/umriss-ui/table/first-table/#first-table), [Search and page from a header of your own](https://romanhaendler.github.io/umriss-ui/table/first-table/#search-and-page-from-a-header-of-your-own), [Sort, arrange and export from controls of your own](https://romanhaendler.github.io/umriss-ui/table/first-table/#sort-arrange-and-export-from-controls-of-your-own) and 118 more |

#### Events

| Prop | Type | Default | Description |
|---|---|---|---|
| `onCellEdit` | `(edit: `[`CellEdit`](https://romanhaendler.github.io/umriss-ui/table/first-table/#type-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.<br>Shown in: [Combine grid mode with groups, pinning and many rows (Grid mode)](https://romanhaendler.github.io/umriss-ui/table/grid-mode/#the-whole-grid), [Edit a comments column (Edits)](https://romanhaendler.github.io/umriss-ui/table/edits/#comments-column), [Check an edit before it is reported (Edits)](https://romanhaendler.github.io/umriss-ui/table/edits/#validated-edits) and 1 more |
| `onRowSave` | `(save: `[`RowSave`](https://romanhaendler.github.io/umriss-ui/table/first-table/#type-RowSave)`<Z>) => void` | — | `editMode="row"`: a saved Row draft, with the columns it changed.<br>Shown in: [Save a row on purpose (Edits)](https://romanhaendler.github.io/umriss-ui/table/edits/#save-a-row-on-purpose), [Check a whole row before it is saved (Edits)](https://romanhaendler.github.io/umriss-ui/table/edits/#check-a-whole-row), [Keep a list: add, change and delete rows (Edits)](https://romanhaendler.github.io/umriss-ui/table/edits/#keep-a-list) and 1 more |
| `onRowAdd` | `(add: `[`RowAdd`](https://romanhaendler.github.io/umriss-ui/table/first-table/#type-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.<br>Shown in: [Keep a list: add, change and delete rows (Edits)](https://romanhaendler.github.io/umriss-ui/table/edits/#keep-a-list) |
| `onRowDelete` | `(remove: `[`RowDelete`](https://romanhaendler.github.io/umriss-ui/table/first-table/#type-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.<br>Shown in: [Keep a list: add, change and delete rows (Edits)](https://romanhaendler.github.io/umriss-ui/table/edits/#keep-a-list), [Edit a dense table (Edits)](https://romanhaendler.github.io/umriss-ui/table/edits/#edit-a-dense-table) |

#### Accessibility

| Prop | Type | Default | Description |
|---|---|---|---|
| `ariaLabel` | `string` | — | Accessible name of the table (not of the outer frame).<br>Shown in: [Show a list of services](https://romanhaendler.github.io/umriss-ui/table/first-table/#first-table), [Search and page from a header of your own](https://romanhaendler.github.io/umriss-ui/table/first-table/#search-and-page-from-a-header-of-your-own), [Sort, arrange and export from controls of your own](https://romanhaendler.github.io/umriss-ui/table/first-table/#sort-arrange-and-export-from-controls-of-your-own) and 118 more |

#### Styling

| Prop | Type | Default | Description |
|---|---|---|---|
| `className` | `string` | — | Classes on the outer frame of the table.<br>Shown in: [Pin columns to both sides (Width and pinning)](https://romanhaendler.github.io/umriss-ui/table/width-and-pinning/#pinned-both-sides), [Keep the header and the names in view (Width and pinning)](https://romanhaendler.github.io/umriss-ui/table/width-and-pinning/#sticky-parts), [Pin and size from controls of your own (Width and pinning)](https://romanhaendler.github.io/umriss-ui/table/width-and-pinning/#pin-and-size-from-controls-of-your-own) and 2 more |

### `TableOptions<Z>`

| Prop | Type | Default | Description |
|---|---|---|---|
| `rowKey` *required* | `(row: Z) => string` | — | A stable key per row – the basis of selection and expansion.<br>Shown in: [Show a list of services](https://romanhaendler.github.io/umriss-ui/table/first-table/#first-table), [Search and page from a header of your own](https://romanhaendler.github.io/umriss-ui/table/first-table/#search-and-page-from-a-header-of-your-own), [Sort, arrange and export from controls of your own](https://romanhaendler.github.io/umriss-ui/table/first-table/#sort-arrange-and-export-from-controls-of-your-own) and 118 more |
| `pageSize` | `number` | `10` | How many rows a page holds.<br>Shown in: [Search and page from a header of your own](https://romanhaendler.github.io/umriss-ui/table/first-table/#search-and-page-from-a-header-of-your-own), [Ask a question over several fields (Filter)](https://romanhaendler.github.io/umriss-ui/table/filter/#a-question-over-several-fields), [Restrict which rows a table has (Pre-filter)](https://romanhaendler.github.io/umriss-ui/table/pre-filter/#pre-filter) and 7 more |
| `defaultSort` | [`SortLevel`](https://romanhaendler.github.io/umriss-ui/table/first-table/#type-SortLevel)`  \| readonly  `[`SortLevel`](https://romanhaendler.github.io/umriss-ui/table/first-table/#type-SortLevel)`[] \| null` | `null` | The application's sort; it is the default that `view` leaves out.<br>Shown in: [Sort, arrange and export from controls of your own](https://romanhaendler.github.io/umriss-ui/table/first-table/#sort-arrange-and-export-from-controls-of-your-own), [A label wider than its column (Width and pinning)](https://romanhaendler.github.io/umriss-ui/table/width-and-pinning/#a-label-wider-than-its-column), [Sort by several columns (Sorting)](https://romanhaendler.github.io/umriss-ui/table/sorting/#sort-by-several-columns) and 10 more |
| `defaultGrouping` | [`GroupingId`](https://romanhaendler.github.io/umriss-ui/table/first-table/#type-GroupingId)`<Z> \| readonly `[`GroupingId`](https://romanhaendler.github.io/umriss-ui/table/first-table/#type-GroupingId)`<Z>[]` | ungrouped, until the user groups | The application's grouping: a column or group key, or up to three, the outermost first. It is the default that `view` leaves out.<br>Shown in: [Group by one column (Grouping)](https://romanhaendler.github.io/umriss-ui/table/grouping/#one-level), [Group on two levels (Grouping)](https://romanhaendler.github.io/umriss-ui/table/grouping/#two-levels), [Group on three levels (Grouping)](https://romanhaendler.github.io/umriss-ui/table/grouping/#three-levels) 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.<br>Shown in: [Restrict which rows a table has (Pre-filter)](https://romanhaendler.github.io/umriss-ui/table/pre-filter/#pre-filter) |
| `rowFilters` | `readonly `[`RowFilter`](https://romanhaendler.github.io/umriss-ui/table/filter/#type-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.<br>Shown in: [Filter by a field no column shows (Filter)](https://romanhaendler.github.io/umriss-ui/table/filter/#a-field-no-column-shows), [Rows from a request (Filter)](https://romanhaendler.github.io/umriss-ui/table/filter/#rows-from-a-request), [Ask a question over several fields (Filter)](https://romanhaendler.github.io/umriss-ui/table/filter/#a-question-over-several-fields) and 3 more |
| `initialView` | [`TableView`](https://romanhaendler.github.io/umriss-ui/table/first-table/#type-TableView) | — | 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.<br>Shown in: [Ask a question over several fields (Filter)](https://romanhaendler.github.io/umriss-ui/table/filter/#a-question-over-several-fields), [Fold groups into a summary (Grouping)](https://romanhaendler.github.io/umriss-ui/table/grouping/#the-overview), [Show loading, empty, failed and no match (Row appearance)](https://romanhaendler.github.io/umriss-ui/table/row-appearance/#states) and 2 more |
| `virtual` | `boolean \| { 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](https://github.com/romanhaendler/umriss-ui/blob/main/docs/adr/0042-a-rows-height-never-follows-what-it-shows.md)).<br>Shown in: [Group twenty thousand rows (Grouping)](https://romanhaendler.github.io/umriss-ui/table/grouping/#twenty-thousand-rows), [Scroll twenty thousand rows (Virtualisation)](https://romanhaendler.github.io/umriss-ui/table/virtualisation/#virtualisation), [Measure two hundred thousand rows (Benchmark)](https://romanhaendler.github.io/umriss-ui/table/benchmark/#benchmark) and 1 more |
| `selection` | [`TableSelection`](https://romanhaendler.github.io/umriss-ui/table/first-table/#type-TableSelection)`<string>` | — | A selection from outside instead of its own – when the application holds it.<br>Shown in: [Hold the selection yourself (Selection)](https://romanhaendler.github.io/umriss-ui/table/selection/#hold-the-selection-yourself) |

#### Events

| Prop | Type | Default | Description |
|---|---|---|---|
| `onViewChange` | `(view: `[`TableView`](https://romanhaendler.github.io/umriss-ui/table/first-table/#type-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.<br>Shown in: [Keep and restore a view (View)](https://romanhaendler.github.io/umriss-ui/table/view/#initial-view) |

### `TableSnapshot<Z>`

| Prop | Type | Default | Description |
|---|---|---|---|
| `rows` *required* | `readonly Z[]` | — | All the rows passed in, including those the pre-filter does not admit.<br>Shown in: [Search and page from a header of your own](https://romanhaendler.github.io/umriss-ui/table/first-table/#search-and-page-from-a-header-of-your-own) |
| `filtered` *required* | `readonly Z[]` | — | The filtered set: what the pre-filter admits and search and filters leave, across all pages.<br>Shown in: [Select rows (Selection)](https://romanhaendler.github.io/umriss-ui/table/selection/#selection), [Scroll twenty thousand rows (Virtualisation)](https://romanhaendler.github.io/umriss-ui/table/virtualisation/#virtualisation) |
| `visible` *required* | `readonly Z[]` | — | What is rendered right now: the page or the window.<br>Shown in: [Scroll twenty thousand rows (Virtualisation)](https://romanhaendler.github.io/umriss-ui/table/virtualisation/#virtualisation) |
| `page` *required* | `number` | — | The page shown, from 1. Without `Pagination` always 1.<br>Shown in: [Search and page from a header of your own](https://romanhaendler.github.io/umriss-ui/table/first-table/#search-and-page-from-a-header-of-your-own) |
| `pageCount` *required* | `number` | — | How many pages the filtered set makes.<br>Shown in: [Search and page from a header of your own](https://romanhaendler.github.io/umriss-ui/table/first-table/#search-and-page-from-a-header-of-your-own) |
| `pageSize` *required* | `number` | — | Rows per page.<br>Shown in: [Search and page from a header of your own](https://romanhaendler.github.io/umriss-ui/table/first-table/#search-and-page-from-a-header-of-your-own) |
| `setPage` *required* | `(page: number) => void` | — | Changes the page, clamped to those that exist.<br>Shown in: [Search and page from a header of your own](https://romanhaendler.github.io/umriss-ui/table/first-table/#search-and-page-from-a-header-of-your-own) |
| `setPageSize` *required* | `(size: number) => void` | — | Sets the rows per page; back to page one.<br>Shown in: [Search and page from a header of your own](https://romanhaendler.github.io/umriss-ui/table/first-table/#search-and-page-from-a-header-of-your-own) |
| `search` *required* | `string` | — | The search text.<br>Shown in: [Search and page from a header of your own](https://romanhaendler.github.io/umriss-ui/table/first-table/#search-and-page-from-a-header-of-your-own) |
| `setSearch` *required* | `(search: string) => void` | — | Sets the search text; back to page one.<br>Shown in: [Search and page from a header of your own](https://romanhaendler.github.io/umriss-ui/table/first-table/#search-and-page-from-a-header-of-your-own) |
| `sort` *required* | `readonly `[`SortLevel`](https://romanhaendler.github.io/umriss-ui/table/first-table/#type-SortLevel)`[]` | — | The sort levels, the first one first – only over columns that registered.<br>Shown in: [Sort, arrange and export from controls of your own](https://romanhaendler.github.io/umriss-ui/table/first-table/#sort-arrange-and-export-from-controls-of-your-own) |
| `toggleSort` *required* | `(column: string, additive?: boolean) => void` | — | Sorts by a column. With `additive` a level is added instead of replacing the sort.<br>Shown in: [Sort, arrange and export from controls of your own](https://romanhaendler.github.io/umriss-ui/table/first-table/#sort-arrange-and-export-from-controls-of-your-own) |
| `hidden` *required* | `readonly string[]` | — | The ids of the hidden columns.<br>Shown in: [Sort, arrange and export from controls of your own](https://romanhaendler.github.io/umriss-ui/table/first-table/#sort-arrange-and-export-from-controls-of-your-own) |
| `toggleColumn` *required* | `(column: string) => void` | — | Hides a column or shows it again. The row header stays.<br>Shown in: [Sort, arrange and export from controls of your own](https://romanhaendler.github.io/umriss-ui/table/first-table/#sort-arrange-and-export-from-controls-of-your-own) |
| `order` *required* | `readonly string[]` | — | The order of the columns as ids, as the user chose it.<br>Shown in: [Sort, arrange and export from controls of your own](https://romanhaendler.github.io/umriss-ui/table/first-table/#sort-arrange-and-export-from-controls-of-your-own) |
| `setOrder` *required* | `(order: readonly string[]) => void` | — | Reorders the columns – on header, body and footer at once.<br>Shown in: [Sort, arrange and export from controls of your own](https://romanhaendler.github.io/umriss-ui/table/first-table/#sort-arrange-and-export-from-controls-of-your-own) |
| `pinned` *required* | `Readonly<Record<string, `[`Pin`](https://romanhaendler.github.io/umriss-ui/table/first-table/#type-Pin)`>>` | — | The pinned columns by id - the declared ones until the user chooses.<br>Shown in: [Pin and size from controls of your own (Width and pinning)](https://romanhaendler.github.io/umriss-ui/table/width-and-pinning/#pin-and-size-from-controls-of-your-own) |
| `setPin` *required* | `(column: string, pin: `[`Pin`](https://romanhaendler.github.io/umriss-ui/table/first-table/#type-Pin)` \| null) => void` | — | Pins a column to the start or the end, or unpins it with `null`.<br>Shown in: [Pin and size from controls of your own (Width and pinning)](https://romanhaendler.github.io/umriss-ui/table/width-and-pinning/#pin-and-size-from-controls-of-your-own) |
| `widths` *required* | `Readonly<Record<string, number>>` | — | The widths in pixels: those of the columns, and over them the dragged ones.<br>Shown in: [Pin and size from controls of your own (Width and pinning)](https://romanhaendler.github.io/umriss-ui/table/width-and-pinning/#pin-and-size-from-controls-of-your-own) |
| `setWidth` *required* | `(column: string, width: number \| undefined) => void` | — | Sets the width of a column; `undefined` takes it back.<br>Shown in: [Pin and size from controls of your own (Width and pinning)](https://romanhaendler.github.io/umriss-ui/table/width-and-pinning/#pin-and-size-from-controls-of-your-own) |
| `expanded` *required* | `readonly string[]` | — | The keys of the expanded rows.<br>Shown in: [Open a detail from outside the table (RowDetail)](https://romanhaendler.github.io/umriss-ui/table/rowdetail/#open-a-detail-from-outside) |
| `toggleRow` *required* | `(key: string) => void` | — | Expands a row or collapses it.<br>Shown in: [Open a detail from outside the table (RowDetail)](https://romanhaendler.github.io/umriss-ui/table/rowdetail/#open-a-detail-from-outside) |
| `filter` *required* | `Readonly<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.<br>Shown in: [Set a condition from outside the table (Filter)](https://romanhaendler.github.io/umriss-ui/table/filter/#conditions-from-outside) |
| `setFilter` *required* | [`SetFilter`](https://romanhaendler.github.io/umriss-ui/table/first-table/#type-SetFilter)`<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.<br>Shown in: [Set a condition from outside the table (Filter)](https://romanhaendler.github.io/umriss-ui/table/filter/#conditions-from-outside), [Filter by a field no column shows (Filter)](https://romanhaendler.github.io/umriss-ui/table/filter/#a-field-no-column-shows), [Rows from a request (Filter)](https://romanhaendler.github.io/umriss-ui/table/filter/#rows-from-a-request) and 5 more |
| `conditionOf` *required* | `<B>(filter: `[`RowFilter`](https://romanhaendler.github.io/umriss-ui/table/filter/#type-RowFilter)`<Z, B>) => B \| null` | — | The condition of a row filter, as its type - `null` while it is lifted.<br>Shown in: [Filter by a field no column shows (Filter)](https://romanhaendler.github.io/umriss-ui/table/filter/#a-field-no-column-shows), [Rows from a request (Filter)](https://romanhaendler.github.io/umriss-ui/table/filter/#rows-from-a-request), [Ask a question over several fields (Filter)](https://romanhaendler.github.io/umriss-ui/table/filter/#a-question-over-several-fields) and 3 more |
| `selection` *required* | [`TableSelection`](https://romanhaendler.github.io/umriss-ui/table/first-table/#type-TableSelection)`<string>` | — | The selection – its own or the one handed in through `selection`.<br>Shown in: [Select rows (Selection)](https://romanhaendler.github.io/umriss-ui/table/selection/#selection), [Copy the selection (Export)](https://romanhaendler.github.io/umriss-ui/table/export/#copy-the-selection), [Scroll twenty thousand rows (Virtualisation)](https://romanhaendler.github.io/umriss-ui/table/virtualisation/#virtualisation) and 3 more |
| `grouping` *required* | `readonly string[]` | — | The ids the table is grouped by, the outermost first – only those a groupable column or a group key carries, at most three.<br>Shown in: [Group and fold from controls of your own (Grouping)](https://romanhaendler.github.io/umriss-ui/table/grouping/#group-and-fold-from-controls-of-your-own) |
| `setGrouping` *required* | `(grouping: readonly string[]) => void` | — | Groups by these columns or group keys; `[]` ungroups. Back to page one.<br>Shown in: [Group and fold from controls of your own (Grouping)](https://romanhaendler.github.io/umriss-ui/table/grouping/#group-and-fold-from-controls-of-your-own) |
| `setView` *required* | `(view: `[`TableView`](https://romanhaendler.github.io/umriss-ui/table/first-table/#type-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.<br>Shown in: [Keep and restore a view (View)](https://romanhaendler.github.io/umriss-ui/table/view/#initial-view) |
| `folded` *required* | `readonly string[]` | — | The paths of the folded groups that still occur.<br>Shown in: [Group and fold from controls of your own (Grouping)](https://romanhaendler.github.io/umriss-ui/table/grouping/#group-and-fold-from-controls-of-your-own) |
| `toggleFold` *required* | `(path: string) => void` | — | Folds a group or unfolds it. A group of one row does not fold.<br>Shown in: [Group and fold from controls of your own (Grouping)](https://romanhaendler.github.io/umriss-ui/table/grouping/#group-and-fold-from-controls-of-your-own) |
| `foldAll` *required* | `() => void` | — | Folds every group that has more than one row.<br>Shown in: [Fold groups into a summary (Grouping)](https://romanhaendler.github.io/umriss-ui/table/grouping/#the-overview) |
| `unfoldAll` *required* | `() => void` | — | Unfolds every group.<br>Shown in: [Fold groups into a summary (Grouping)](https://romanhaendler.github.io/umriss-ui/table/grouping/#the-overview) |
| `branches` *required* | `readonly string[]` | — | Tree rows: the keys of the open branches.<br>Shown in: [Open branches from controls of your own (Tree rows)](https://romanhaendler.github.io/umriss-ui/table/tree-rows/#open-branches-from-controls-of-your-own) |
| `toggleBranch` *required* | `(key: string) => void` | — | Tree rows: opens a branch or closes it.<br>Shown in: [Open branches from controls of your own (Tree rows)](https://romanhaendler.github.io/umriss-ui/table/tree-rows/#open-branches-from-controls-of-your-own) |
| `unfoldAllBranches` *required* | `() => void` | — | Tree rows: opens every branch that has children.<br>Shown in: [Open branches from controls of your own (Tree rows)](https://romanhaendler.github.io/umriss-ui/table/tree-rows/#open-branches-from-controls-of-your-own) |
| `foldAllBranches` *required* | `() => void` | — | Tree rows: closes every branch.<br>Shown in: [Open branches from controls of your own (Tree rows)](https://romanhaendler.github.io/umriss-ui/table/tree-rows/#open-branches-from-controls-of-your-own) |
| `view` *required* | [`TableView`](https://romanhaendler.github.io/umriss-ui/table/first-table/#type-TableView) | — | The part of the state an application can keep; whatever still stands as the options set it up is absent.<br>Shown in: [Set a condition from outside the table (Filter)](https://romanhaendler.github.io/umriss-ui/table/filter/#conditions-from-outside), [Fold groups into a summary (Grouping)](https://romanhaendler.github.io/umriss-ui/table/grouping/#the-overview), [Keep and restore a view (View)](https://romanhaendler.github.io/umriss-ui/table/view/#initial-view) |
| `asCsv` *required* | `() => string` | — | The filtered set in the visible columns as text for a spreadsheet - in server mode the page.<br>Shown in: [Sort, arrange and export from controls of your own](https://romanhaendler.github.io/umriss-ui/table/first-table/#sort-arrange-and-export-from-controls-of-your-own) |
| `selectionAsText` *required* | `() => string` | — | The selected rows within the filtered set as tab-separated text under a header line - the text Ctrl+C writes. `""` with nothing selected.<br>Shown in: [Copy the selection (Export)](https://romanhaendler.github.io/umriss-ui/table/export/#copy-the-selection) |
| `virtual` *required* | `boolean` | — | Whether it virtualises.<br>Shown in: [Search and page from a header of your own](https://romanhaendler.github.io/umriss-ui/table/first-table/#search-and-page-from-a-header-of-your-own) |
| `rowCount` *required* | `number` | — | How many rows the filtered set has - in server mode the server's count.<br>Shown in: [Search and page from a header of your own](https://romanhaendler.github.io/umriss-ui/table/first-table/#search-and-page-from-a-header-of-your-own) |
| `server` *required* | `boolean` | — | Whether it is in server mode: the rows are one page of a server's.<br>Shown in: [Sort, arrange and export from controls of your own](https://romanhaendler.github.io/umriss-ui/table/first-table/#sort-arrange-and-export-from-controls-of-your-own) |

### Types on this page

#### `RowAttributes`

What a row carries in classes and data attributes.

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

#### `CellEdit<Z>`

An edit the user committed - reported, not applied.

| Prop | Type | Default | Description |
|---|---|---|---|
| `rowKey` *required* | `string` | — | <br>Shown in: [Combine grid mode with groups, pinning and many rows (Grid mode)](https://romanhaendler.github.io/umriss-ui/table/grid-mode/#the-whole-grid), [Edit a comments column (Edits)](https://romanhaendler.github.io/umriss-ui/table/edits/#comments-column), [Check an edit before it is reported (Edits)](https://romanhaendler.github.io/umriss-ui/table/edits/#validated-edits) and 1 more |
| `columnId` *required* | `string` | — | <br>Shown in: [Combine grid mode with groups, pinning and many rows (Grid mode)](https://romanhaendler.github.io/umriss-ui/table/grid-mode/#the-whole-grid), [Check an edit before it is reported (Edits)](https://romanhaendler.github.io/umriss-ui/table/edits/#validated-edits), [Plan the team's capacity (Scenarios)](https://romanhaendler.github.io/umriss-ui/table/#plan-the-team-capacity) |
| `value` *required* | `unknown` | — | The editor's value: text for `"text"`, a number or null for `"number"`, a day or null for `"date"`, an option for `"select"`.<br>Shown in: [Combine grid mode with groups, pinning and many rows (Grid mode)](https://romanhaendler.github.io/umriss-ui/table/grid-mode/#the-whole-grid), [Edit a comments column (Edits)](https://romanhaendler.github.io/umriss-ui/table/edits/#comments-column), [Check an edit before it is reported (Edits)](https://romanhaendler.github.io/umriss-ui/table/edits/#validated-edits) and 1 more |
| `row` *required* | `Z` | — | <br>Shown in: [Check an edit before it is reported (Edits)](https://romanhaendler.github.io/umriss-ui/table/edits/#validated-edits), [Plan the team's capacity (Scenarios)](https://romanhaendler.github.io/umriss-ui/table/#plan-the-team-capacity) |

#### `RowSave<Z>`

A Row draft the user saved ([ADR-0036](https://github.com/romanhaendler/umriss-ui/blob/main/docs/adr/0036-a-grid-edits-on-the-first-click-and-saves-rows-on-purpose.md)) - reported, not applied.

| Prop | Type | Default | Description |
|---|---|---|---|
| `rowKey` *required* | `string` | — | <br>Shown in: [Save a row on purpose (Edits)](https://romanhaendler.github.io/umriss-ui/table/edits/#save-a-row-on-purpose), [Check a whole row before it is saved (Edits)](https://romanhaendler.github.io/umriss-ui/table/edits/#check-a-whole-row), [Keep a list: add, change and delete rows (Edits)](https://romanhaendler.github.io/umriss-ui/table/edits/#keep-a-list) and 1 more |
| `changes` *required* | `Readonly<Record<string, unknown>>` | — | The columns the draft changed, by column id, with their editors' values.<br>Shown in: [Save a row on purpose (Edits)](https://romanhaendler.github.io/umriss-ui/table/edits/#save-a-row-on-purpose), [Check a whole row before it is saved (Edits)](https://romanhaendler.github.io/umriss-ui/table/edits/#check-a-whole-row), [Keep a list: add, change and delete rows (Edits)](https://romanhaendler.github.io/umriss-ui/table/edits/#keep-a-list) and 1 more |
| `row` *required* | `Z` | — | <br>Shown in: [Save a row on purpose (Edits)](https://romanhaendler.github.io/umriss-ui/table/edits/#save-a-row-on-purpose) |

#### `RowAdd`

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

| Prop | Type | Default | Description |
|---|---|---|---|
| `values` *required* | `Readonly<Record<string, unknown>>` | — | <br>Shown in: [Keep a list: add, change and delete rows (Edits)](https://romanhaendler.github.io/umriss-ui/table/edits/#keep-a-list) |

#### `RowDelete<Z>`

A row the user asked to delete, and confirmed.

| Prop | Type | Default | Description |
|---|---|---|---|
| `rowKey` *required* | `string` | — | <br>Shown in: [Keep a list: add, change and delete rows (Edits)](https://romanhaendler.github.io/umriss-ui/table/edits/#keep-a-list), [Edit a dense table (Edits)](https://romanhaendler.github.io/umriss-ui/table/edits/#edit-a-dense-table) |
| `row` *required* | `Z` | — |  |

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

| Prop | Type | Default | Description |
|---|---|---|---|
| `column` *required* | `K` | — | <br>Shown in: [Sort, arrange and export from controls of your own](https://romanhaendler.github.io/umriss-ui/table/first-table/#sort-arrange-and-export-from-controls-of-your-own), [A label wider than its column (Width and pinning)](https://romanhaendler.github.io/umriss-ui/table/width-and-pinning/#a-label-wider-than-its-column), [Sort by several columns (Sorting)](https://romanhaendler.github.io/umriss-ui/table/sorting/#sort-by-several-columns) and 10 more |
| `direction` *required* | [`SortDirection`](https://romanhaendler.github.io/umriss-ui/table/first-table/#type-SortDirection)<br>`"asc" \| "desc"` | — | <br>Shown in: [Sort, arrange and export from controls of your own](https://romanhaendler.github.io/umriss-ui/table/first-table/#sort-arrange-and-export-from-controls-of-your-own), [A label wider than its column (Width and pinning)](https://romanhaendler.github.io/umriss-ui/table/width-and-pinning/#a-label-wider-than-its-column), [Sort by several columns (Sorting)](https://romanhaendler.github.io/umriss-ui/table/sorting/#sort-by-several-columns) and 10 more |

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

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

| Prop | Type | Default | Description |
|---|---|---|---|
| `search` | `string` | — | <br>Shown in: [Show loading, empty, failed and no match (Row appearance)](https://romanhaendler.github.io/umriss-ui/table/row-appearance/#states), [Hand the table one page (Server mode)](https://romanhaendler.github.io/umriss-ui/table/server-mode/#only-pages), [Sort and search on the server (Server mode)](https://romanhaendler.github.io/umriss-ui/table/server-mode/#sort-and-search) and 3 more |
| `conditions` | `Readonly<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.<br>Shown in: [Ask a question over several fields (Filter)](https://romanhaendler.github.io/umriss-ui/table/filter/#a-question-over-several-fields), [A dispatcher's bar (Toolbar controls)](https://romanhaendler.github.io/umriss-ui/table/toolbar-controls/#a-dispatcher-s-bar), [Hand the table one page (Server mode)](https://romanhaendler.github.io/umriss-ui/table/server-mode/#only-pages) and 4 more |
| `sort` | `readonly `[`SortLevel`](https://romanhaendler.github.io/umriss-ui/table/first-table/#type-SortLevel)`<K>[]` | — | <br>Shown in: [Keep and restore a view (View)](https://romanhaendler.github.io/umriss-ui/table/view/#initial-view), [Hand the table one page (Server mode)](https://romanhaendler.github.io/umriss-ui/table/server-mode/#only-pages), [Sort and search on the server (Server mode)](https://romanhaendler.github.io/umriss-ui/table/server-mode/#sort-and-search) and 3 more |
| `page` | `number` | — | <br>Shown in: [Hand the table one page (Server mode)](https://romanhaendler.github.io/umriss-ui/table/server-mode/#only-pages), [Sort and search on the server (Server mode)](https://romanhaendler.github.io/umriss-ui/table/server-mode/#sort-and-search), [Name the values a list filter offers (Server mode)](https://romanhaendler.github.io/umriss-ui/table/server-mode/#filter-options) and 2 more |
| `pageSize` | `number` | — | <br>Shown in: [Hand the table one page (Server mode)](https://romanhaendler.github.io/umriss-ui/table/server-mode/#only-pages), [Sort and search on the server (Server mode)](https://romanhaendler.github.io/umriss-ui/table/server-mode/#sort-and-search), [Name the values a list filter offers (Server mode)](https://romanhaendler.github.io/umriss-ui/table/server-mode/#filter-options) and 2 more |
| `hidden` | `readonly K[]` | — | <br>Shown in: [Keep and restore a view (View)](https://romanhaendler.github.io/umriss-ui/table/view/#initial-view) |
| `order` | `readonly K[]` | — |  |
| `widths` | `Readonly<Record<string, number>>` | — | Column widths the user has dragged, in pixels. |
| `grouping` | `readonly K[]` | — | The columns and group keys the table is grouped by, the outermost first. |
| `folded` | `readonly string[]` | — | The paths of the folded groups. A path that no longer occurs falls out.<br>Shown in: [Fold groups into a summary (Grouping)](https://romanhaendler.github.io/umriss-ui/table/grouping/#the-overview) |
| `branches` | `readonly string[]` | — | Tree rows: the keys of the open branches. A key that no longer occurs falls out. |
| `pinned` | `Readonly<Record<string, `[`Pin`](https://romanhaendler.github.io/umriss-ui/table/first-table/#type-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.

| Prop | Type | Default | Description |
|---|---|---|---|
| `selected` *required* | `ReadonlySet<T>` | — | The keys currently selected.<br>Shown in: [Work through the alerts (Scenarios)](https://romanhaendler.github.io/umriss-ui/table/#work-through-the-alerts) |
| `isSelected` *required* | `(id: T) => boolean` | — | <br>Shown in: [Select rows (Selection)](https://romanhaendler.github.io/umriss-ui/table/selection/#selection), [Hold the selection yourself (Selection)](https://romanhaendler.github.io/umriss-ui/table/selection/#hold-the-selection-yourself) |
| `toggle` *required* | `(id: T) => void` | — | <br>Shown in: [Hold the selection yourself (Selection)](https://romanhaendler.github.io/umriss-ui/table/selection/#hold-the-selection-yourself) |
| `toggleAll` *required* | `() => void` | — | Selects all the keys passed in - or, when all of them are selected already, deselects them. Keys outside them stay as they are. |
| `clear` *required* | `() => void` | — | <br>Shown in: [Hold the selection yourself (Selection)](https://romanhaendler.github.io/umriss-ui/table/selection/#hold-the-selection-yourself), [Work through the alerts (Scenarios)](https://romanhaendler.github.io/umriss-ui/table/#work-through-the-alerts), [Find a late shipment (Scenarios)](https://romanhaendler.github.io/umriss-ui/table/#find-a-late-shipment) |
| `allSelected` *required* | `boolean` | — | true when every key passed in is selected. |
| `someSelected` *required* | `boolean` | — | true when some - but not all - are selected. |
| `count` *required* | `number` | — | <br>Shown in: [Select rows (Selection)](https://romanhaendler.github.io/umriss-ui/table/selection/#selection), [Hold the selection yourself (Selection)](https://romanhaendler.github.io/umriss-ui/table/selection/#hold-the-selection-yourself), [Copy the selection (Export)](https://romanhaendler.github.io/umriss-ui/table/export/#copy-the-selection) and 2 more |

#### `Pin`

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

```ts
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](https://github.com/romanhaendler/umriss-ui/blob/main/docs/adr/0017-columns-are-declared-by-composition.md)). 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.

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

```ts
type SortDirection = "asc" | "desc";
```

#### `Field<Z>`

The field names of a row.

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

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

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

#### `Present<W>`

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

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

#### `RangeCondition<W>`

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

| Prop | Type | Default | Description |
|---|---|---|---|
| `from` | `W` | — |  |
| `to` | `W` | — |  |

#### `Absent`

A value that is not there: `null` or `undefined`.

```ts
type Absent = null | undefined;
```

## Known limits

- No cell range selection and no undo: an edit is reported, never applied, and the application owns the rows ([ADR-0032](https://romanhaendler.github.io/umriss-ui/what-umriss-ui-is-not/)).
- No pivot: compute the pivoted rows and declare their columns ([ADR-0032](https://romanhaendler.github.io/umriss-ui/what-umriss-ui-is-not/)).

What umriss deliberately does not build, and why: [ADR-0032](https://romanhaendler.github.io/umriss-ui/what-umriss-ui-is-not/).
