# Row appearance

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

How rows look: dense or regular, marked by their state, and what stands while rows load, fail or do not exist.

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

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

A row's mark is the application's stylesheet at work; say the same in a word in a column too, so the colour is never the only sign.

## Examples

### Density

`density="compact"` sets one table; an application that wants it dense everywhere says so once at the `UmrissProvider`.

```tsx
import { UmrissProvider, Stack, Text } from "@umriss-ui/core";
import { useTable } from "@umriss-ui/table";

interface Service {
  name: string;
  team: string;
  p95: number;
}

const SERVICES: Service[] = [
  { name: "Checkout", team: "Payments", p95: 412 },
  { name: "Billing", team: "Payments", p95: 238 },
  { name: "Sign-in", team: "Identity", p95: 141 },
];

function Services({ caption, density }: { caption: string; density?: "regular" | "compact" }) {
  const { Table, Column } = useTable(SERVICES, { rowKey: (s) => s.name });
  return (
    <Stack gap={2}>
      <Text size="xs" tone="muted">
        {caption}
      </Text>
      <Table density={density} ariaLabel={`Services, ${caption}`}>
        <Column value="name" label="Service" rowHeader />
        <Column value="team" label="Team" />
        <Column value="p95" label="p95 latency (ms)" />
      </Table>
    </Stack>
  );
}

export default function Density() {
  return (
    <Stack gap={5}>
      <Services caption="Without a statement: regular" />
      <Services caption='density="compact"' density="compact" />
      <UmrissProvider density="compact">
        <Services caption="From the UmrissProvider: compact" />
      </UmrissProvider>
    </Stack>
  );
}
```

### Mark rows by their state

`rowProps` gives a row classes and data attributes; the application's stylesheet draws them, and a word in a column says the same.

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

interface Invoice {
  id: string;
  supplier: string;
  due: Date;
  overdue: boolean;
}

const INVOICES: Invoice[] = [
  { id: "INV-26-0318", supplier: "Brandlow Office Supply", due: new Date(2026, 3, 15), overdue: false },
  { id: "INV-26-0309", supplier: "Nimbrel Software", due: new Date(2026, 3, 8), overdue: false },
  { id: "INV-26-0226", supplier: "Corrin Travel", due: new Date(2026, 2, 12), overdue: true },
  { id: "INV-26-0221", supplier: "Stellbrook Consulting", due: new Date(2026, 2, 16), overdue: true },
];

const STYLE = `
.invoice-list tr[data-overdue] > th {
  box-shadow: inset 3px 0 0 var(--u-color-warning);
}
`;

export default function StylingRows() {
  const { Table, Column } = useTable(INVOICES, { rowKey: (i) => i.id });

  return (
    <>
      <style>{STYLE}</style>
      <Table className="invoice-list" ariaLabel="Invoices" rowProps={(i) => ({ "data-overdue": i.overdue ? "" : undefined })}>
        <Column value="id" label="Invoice" rowHeader />
        <Column value="supplier" label="Supplier" />
        <Column value="due" label="Due" format="date" />
        <Column value="overdue" label="Overdue" />
      </Table>
    </>
  );
}
```

### Show loading, empty, failed and no match

`loading` shows placeholder rows where there are none yet, and dims the rows that stand while new ones are on their way; `empty` what stands without rows, a failed load too; that nothing matches the table recognises itself.

```tsx
import type { ReactNode } from "react";
import { Alert, Button, EmptyState, Stack } from "@umriss-ui/core";
import { Search, Toolbar, useTable } from "@umriss-ui/table";

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

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

const NONE: Incident[] = [];

function Incidents({ rows, label, ...props }: { rows: Incident[]; label: string; loading?: boolean; empty?: ReactNode; search?: string }) {
  const { Table, Column } = useTable(rows, { rowKey: (i) => i.id, initialView: props.search ? { search: props.search } : undefined });
  return (
    <Table ariaLabel={label} loading={props.loading} empty={props.empty}>
      {props.search !== undefined && (
        <Toolbar>
          <Search placeholder="Search incidents" />
        </Toolbar>
      )}
      <Column value="id" label="Incident" rowHeader />
      <Column value="title" label="Title" />
      <Column value="severity" label="Severity" />
    </Table>
  );
}

export default function States() {
  return (
    <Stack gap={5}>
      <Incidents rows={NONE} label="Incidents are loading" loading />
      <Incidents
        rows={NONE}
        label="Open incidents"
        empty={<EmptyState title="No open incidents" description="New incidents appear here as soon as an alert opens one." />}
      />
      <Incidents
        rows={NONE}
        label="Incidents, not loaded"
        empty={
          <Alert tone="danger" title="The incidents could not be loaded" actions={<Button size="sm">Try again</Button>}>
            The status service did not answer within 10 seconds.
          </Alert>
        }
      />
      <Incidents rows={INCIDENTS} label="Incidents, searched" search="database" />
    </Stack>
  );
}
```

### Stripes across a wide table

`striped` shades every other row, so the eye keeps a long row on its line across eight columns, and on a phone while it scrolls sideways.

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

interface Shipment {
  id: string;
  customer: string;
  street: string;
  city: string;
  tour: string;
  driver: string;
  parcels: number;
  weight: number;
}

const SHIPMENTS: Shipment[] = [
  { id: "FP-1004210", customer: "Holloway Garden Supplies", street: "14 Orchard Lane", city: "Ashford", tour: "T-01", driver: "Tomasz Nowak", parcels: 6, weight: 1140 },
  { id: "FP-1004223", customer: "Oakridge Pharmacy", street: "2 Station Road", city: "Wye", tour: "T-01", driver: "Tomasz Nowak", parcels: 2, weight: 420 },
  { id: "FP-1004236", customer: "Brixley Cycles", street: "88 High Street", city: "Charing", tour: "T-02", driver: "Leila Haddad", parcels: 4, weight: 310 },
  { id: "FP-1004249", customer: "Pellham Hardware", street: "5 Forge Yard", city: "Lenham", tour: "T-02", driver: "Leila Haddad", parcels: 9, weight: 1620 },
  { id: "FP-1004262", customer: "Corrin Travel", street: "31 Bank Street", city: "Ashford", tour: "T-03", driver: "Jonas Keller", parcels: 1, weight: 12 },
  { id: "FP-1004275", customer: "Nimbrel Software", street: "7 Kennington Road", city: "Willesborough", tour: "T-03", driver: "Jonas Keller", parcels: 3, weight: 95 },
];

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

  return (
    <Table striped stickyRowHeader ariaLabel="Shipments with their addresses">
      <Column value="id" label="Shipment" rowHeader />
      <Column value="customer" label="Customer" />
      <Column value="street" label="Street" />
      <Column value="city" label="Town" />
      <Column value="tour" label="Tour" />
      <Column value="driver" label="Driver" />
      <Column value="parcels" label="Parcels" />
      <Column value="weight" label="Weight (kg)" />
    </Table>
  );
}
```

## Keyboard

The keys of [First table](https://romanhaendler.github.io/umriss-ui/table/first-table/#keyboard-first-table), [Input](https://romanhaendler.github.io/umriss-ui/core/input/#keyboard-input) and [Button](https://romanhaendler.github.io/umriss-ui/core/button/#keyboard-button) apply here.

## Accessibility

While `loading` is set the table is `aria-busy`: a screen reader waits for the rows instead of reading the placeholders. Rows that stand while new ones load stay readable.

What stands in `empty` is the application's, and it is read where it stands. A failed load that must be heard on arrival goes there as core's `Alert` with `tone="danger"`, which announces itself. That nothing matches a search or a filter the table says itself.

## Props on this page

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

| Prop | Type | Default | Description |
|---|---|---|---|
| [`stickyRowHeader`](https://romanhaendler.github.io/umriss-ui/table/first-table/#TableProps-stickyRowHeader) | `boolean` | — | Keeps the row header column visible while scrolling sideways - the same as `pin="start"` on the row header. |
| [`striped`](https://romanhaendler.github.io/umriss-ui/table/first-table/#TableProps-striped) | `boolean` | — | Discreet zebra stripes. |
| [`density`](https://romanhaendler.github.io/umriss-ui/table/first-table/#TableProps-density) | `"regular" \| "compact"` | the density of the `UmrissProvider`, else `"regular"` | How tightly the rows stand. |
| [`empty`](https://romanhaendler.github.io/umriss-ui/table/first-table/#TableProps-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. |
| [`loading`](https://romanhaendler.github.io/umriss-ui/table/first-table/#TableProps-loading) | `boolean` | — | Shows placeholder rows instead of the rows. |
| [`rowProps`](https://romanhaendler.github.io/umriss-ui/table/first-table/#TableProps-rowProps) | `(row: Z) => `[`RowAttributes`](https://romanhaendler.github.io/umriss-ui/table/first-table/#type-RowAttributes) | — | Classes and data attributes per row. |
| [`children`](https://romanhaendler.github.io/umriss-ui/table/first-table/#TableProps-children) | `ReactNode` | — | Columns, `RowDetail`, `RowActions` and the unbound parts. |

#### Accessibility

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

#### Styling

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

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

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

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

| Prop | Type | Default | Description |
|---|---|---|---|
| [`value`](https://romanhaendler.github.io/umriss-ui/table/column/#FieldColumn-value) *required* | `K` | — | A field of the row. Supplies the column's `id` at the same time. |
| [`format`](https://romanhaendler.github.io/umriss-ui/table/column/#FieldColumn-format) | [`FormatFor`](https://romanhaendler.github.io/umriss-ui/table/column/#type-FormatFor)`<Z[K]>` | — | A standard presentation by name: `"percent"`, `"count"` or `{ decimals }` for numbers, `"date"`, `"time"` or `"dateTime"` for points in time. Alignment, sorting, export and footer stay with the value. |

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

| Prop | Type | Default | Description |
|---|---|---|---|
| [`label`](https://romanhaendler.github.io/umriss-ui/table/column/#ColumnBase-label) *required* | `string` | — | Names the column in its header, in the column menu and in the header row of the export. |
| [`rowHeader`](https://romanhaendler.github.io/umriss-ui/table/column/#ColumnBase-rowHeader) | `boolean` | — | This column names the row: row header cells, never hideable, the column `stickyRowHeader` pins, and the name by which the row's selection, expander and actions are read out. At most one per table. |

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

| Prop | Type | Default | Description |
|---|---|---|---|
| [`placeholder`](https://romanhaendler.github.io/umriss-ui/table/search/#SearchProps-placeholder) | `string` | the wording's "Search …" | The placeholder in the field. |

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

| Prop | Type | Default | Description |
|---|---|---|---|
| [`children`](https://romanhaendler.github.io/umriss-ui/table/toolbar/#ToolbarProps-children) | `ReactNode` | — | Search, column menu, export – on the left in the table toolbar, before the conditions. |
