# Toolbar controls

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

Put controls of your own into the toolbar - a multiselect, a select, a combobox - and let them stand level with the table's: one size for the whole bar, and a row filter behind each control.

```ts
import { Toolbar, rowFilter } from "@umriss-ui/table";
```

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

`Toolbar` takes `size`, `sm` or `md`, and the parts the table puts into it follow: search, column menu, export, “Reset” and the bulk actions. A part says its own size with its own `size`. A control from @umriss-ui/core that you put in takes the toolbar's size as well, without a word - the toolbar is a `ControlSizeProvider`, as @umriss-ui/core's Sizes page shows - and keeps its own `size` if it says one.

No widths to set: a field from @umriss-ui/core is its natural width in the bar - a count of characters of its type, or its `chars` - and never what it shows, so choosing in a multiselect moves nothing beside it.

A control of your own restricts the table through a [row filter](https://romanhaendler.github.io/umriss-ui/table/filter/): it sets the condition with `t.setFilter(filter, condition)` and reads it with `t.conditionOf(filter)`, so “Reset”, the count of matches and the view include it.

## Examples

### Controls of your own at the toolbar's size

The toolbar is `sm` unless it says otherwise, and a control from @umriss-ui/core put into it takes that size without a word - level with the search, at its natural width, whatever it shows. Behind each control a row filter.

```tsx
import { MultiSelect, Select } from "@umriss-ui/core";
import { Pagination, Search, Toolbar, rowFilter, useTable } from "@umriss-ui/table";

/* Data from the logistics world, written out here so the example runs on its own. */
/** A small LCG - the same numbers on every computer. */
function random(seed: number): () => number {
  let s = seed >>> 0;
  return () => {
    s = (Math.imul(s, 1664525) + 1013904223) >>> 0;
    return s / 4294967296;
  };
}

const at = (hours: number, minutes = 0) => new Date(2026, 2, 17, hours, minutes).getTime();
const MINUTE = 60_000;

/** Tuesday, 17 March 2026, 10:30 - the moment the screens are read at. */
const NOW = at(10, 30);

interface Vehicle {
  id: string;
  plate: string;
  type: "van" | "e-van" | "truck";
  /** Payload, in kg. */
  capacity: number;
  depot: string;
}

const VEHICLES: readonly Vehicle[] = [
  { id: "v1", plate: "FP 214 K", type: "van", capacity: 1200, depot: "north" },
  { id: "v2", plate: "FP 377 K", type: "e-van", capacity: 900, depot: "north" },
  { id: "v3", plate: "FP 118 R", type: "truck", capacity: 7500, depot: "north" },
  { id: "v4", plate: "FP 402 R", type: "van", capacity: 1200, depot: "river" },
  { id: "v5", plate: "FP 455 R", type: "e-van", capacity: 900, depot: "river" },
  { id: "v6", plate: "FP 290 E", type: "van", capacity: 1200, depot: "east" },
  { id: "v7", plate: "FP 311 E", type: "e-van", capacity: 900, depot: "east" },
  { id: "v8", plate: "FP 520 E", type: "truck", capacity: 7500, depot: "east" },
];

const DRIVERS = [
  { id: "d1", name: "Martin Hale", depot: "north" },
  { id: "d2", name: "Nadia Petrova", depot: "north" },
  { id: "d3", name: "Owen Carter", depot: "north" },
  { id: "d4", name: "Lucia Romero", depot: "river" },
  { id: "d5", name: "Ben Adeyemi", depot: "river" },
  { id: "d6", name: "Hanna Berg", depot: "east" },
  { id: "d7", name: "Yusuf Demir", depot: "east" },
  { id: "d8", name: "Clara Wendt", depot: "east" },
] as const;

const CUSTOMERS = [
  "Holloway Garden Supplies",
  "Marlow & Finch Books",
  "Oakridge Pharmacy",
  "Brixley Cycles",
  "Tamsin's Bakery",
  "Northfold Office",
  "Pellham Hardware",
  "Greywick Studio",
  "Juniper Lane Florist",
  "Ashcombe Dental",
] as const;

interface Stop {
  id: string;
  shipment: string;
  customer: string;
  /** The window the customer was promised, `[from, to]`. */
  window: readonly [number, number];
  /** When the tour plans to be there. */
  arrival: number;
}

interface Tour {
  id: string;
  vehicle: string;
  driver: string;
  depot: string;
  /** Leaves the depot, loaded; back at the depot. */
  from: number;
  to: number;
  /** Minutes of loading before `from`. */
  loading: number;
  km: number;
  stops: readonly Stop[];
}

/** One tour per vehicle. Stops every half hour or so, each with a two-hour
    window; every seventh arrival misses its window - the late ones a
    dispatcher looks for. */
function tour(index: number): Tour {
  const r = random(700 + index);
  const vehicle = VEHICLES[index]!;
  const truck = vehicle.type === "truck";
  const from = at(truck ? 6 : 7, index % 2 === 0 ? 0 : 30);
  const count = truck ? 5 : 8 + Math.floor(r() * 4);
  const stops: Stop[] = [];
  let arrival = from + (20 + Math.floor(r() * 15)) * MINUTE;
  for (let i = 0; i < count; i++) {
    const n = index * 20 + i;
    const slot = Math.floor((arrival - at(0)) / (2 * 60 * MINUTE)) * 2;
    /* A late stop's window closed half an hour or more before the arrival. */
    const late = i >= 2 && n % 7 === 3;
    const halfHour = 30 * MINUTE;
    const windowFrom = late ? Math.floor(arrival / halfHour) * halfHour - 5 * halfHour : at(slot);
    stops.push({
      id: `s-${index + 1}-${i + 1}`,
      shipment: `FP-${(1_004_210 + n * 13).toString()}`,
      customer: CUSTOMERS[(n * 3) % CUSTOMERS.length]!,
      window: [windowFrom, windowFrom + 2 * 60 * MINUTE],
      arrival,
    });
    arrival += (truck ? 45 : 22) * MINUTE + Math.floor(r() * 18) * MINUTE;
  }
  return {
    id: `T-${String(index + 1).padStart(2, "0")}`,
    vehicle: vehicle.id,
    driver: DRIVERS[index]!.id,
    depot: vehicle.depot,
    from,
    to: arrival + 30 * MINUTE,
    loading: truck ? 45 : 30,
    km: Math.round((truck ? 140 : 70) + r() * 40),
    stops,
  };
}

const TOURS: readonly Tour[] = VEHICLES.map((_, index) => tour(index));

interface Shipment {
  id: string;
  customer: string;
  /** In kg. */
  weight: number;
  status: "delivered" | "out for delivery" | "failed attempt";
  window: readonly [number, number];
  tour: string;
}

/** One shipment per stop. Before now, delivered - or a failed attempt, where
    nobody was in; after now, out for delivery. */
const SHIPMENTS: readonly Shipment[] = TOURS.flatMap((one) =>
  one.stops.map((stop) => {
    const n = Number(stop.shipment.slice(3));
    return {
      id: stop.shipment,
      customer: stop.customer,
      weight: Math.round((VEHICLES.find((v) => v.id === one.vehicle)!.type === "truck" ? 180 : 2) + ((n * 7919) % 230) / 10),
      status: stop.arrival > NOW ? "out for delivery" : n % 11 === 0 ? "failed attempt" : "delivered",
      window: stop.window,
      tour: one.id,
    } satisfies Shipment;
  }),
);

type Status = Shipment["status"];

const tours = rowFilter({
  id: "tours",
  label: "Tours",
  matches: (shipment: Shipment, chosen: readonly string[]) => chosen.includes(shipment.tour),
});

const status = rowFilter({
  id: "status",
  label: "Status",
  matches: (shipment: Shipment, wanted: Status) => shipment.status === wanted,
});

const TOUR_OPTIONS = [...new Set(SHIPMENTS.map((s) => s.tour))].map((tour) => ({ value: tour, label: tour }));

export default function ControlsAtTheToolbarsSize() {
  const t = useTable(SHIPMENTS, { rowKey: (s) => s.id, rowFilters: [tours, status] });
  const { Table, Column } = t;

  return (
    <Table ariaLabel="Shipments">
      <Toolbar>
        <Search placeholder="Shipment or customer" />
        <MultiSelect
          aria-label="Tours"
          placeholder="All tours"
          options={TOUR_OPTIONS}
          value={t.conditionOf(tours) ?? []}
          onChange={(next) => t.setFilter(tours, next.length > 0 ? next : null)}
        />
        <Select
          aria-label="Status"
          value={t.conditionOf(status) ?? ""}
          onChange={(event) => t.setFilter(status, event.target.value === "" ? null : (event.target.value as Status))}
        >
          <option value="">Every status</option>
          <option value="out for delivery">Out for delivery</option>
          <option value="failed attempt">Failed attempt</option>
          <option value="delivered">Delivered</option>
        </Select>
      </Toolbar>
      <Column value="id" label="Shipment" rowHeader />
      <Column value="customer" label="Customer" />
      <Column value="tour" label="Tour" />
      <Column value="status" label="Status" />
      <Pagination />
    </Table>
  );
}
```

### A larger toolbar

`size="md"` on the toolbar, and search, column menu, export, “Reset” and the multiselect of one's own follow. One size for the whole bar, said once.

```tsx
import { MultiSelect } from "@umriss-ui/core";
import { ColumnMenu, Export, Pagination, Search, Toolbar, rowFilter, useTable } from "@umriss-ui/table";

/* Data from the logistics world, written out here so the example runs on its own. */
/** A small LCG - the same numbers on every computer. */
function random(seed: number): () => number {
  let s = seed >>> 0;
  return () => {
    s = (Math.imul(s, 1664525) + 1013904223) >>> 0;
    return s / 4294967296;
  };
}

const at = (hours: number, minutes = 0) => new Date(2026, 2, 17, hours, minutes).getTime();
const MINUTE = 60_000;

/** Tuesday, 17 March 2026, 10:30 - the moment the screens are read at. */
const NOW = at(10, 30);

interface Vehicle {
  id: string;
  plate: string;
  type: "van" | "e-van" | "truck";
  /** Payload, in kg. */
  capacity: number;
  depot: string;
}

const VEHICLES: readonly Vehicle[] = [
  { id: "v1", plate: "FP 214 K", type: "van", capacity: 1200, depot: "north" },
  { id: "v2", plate: "FP 377 K", type: "e-van", capacity: 900, depot: "north" },
  { id: "v3", plate: "FP 118 R", type: "truck", capacity: 7500, depot: "north" },
  { id: "v4", plate: "FP 402 R", type: "van", capacity: 1200, depot: "river" },
  { id: "v5", plate: "FP 455 R", type: "e-van", capacity: 900, depot: "river" },
  { id: "v6", plate: "FP 290 E", type: "van", capacity: 1200, depot: "east" },
  { id: "v7", plate: "FP 311 E", type: "e-van", capacity: 900, depot: "east" },
  { id: "v8", plate: "FP 520 E", type: "truck", capacity: 7500, depot: "east" },
];

const DRIVERS = [
  { id: "d1", name: "Martin Hale", depot: "north" },
  { id: "d2", name: "Nadia Petrova", depot: "north" },
  { id: "d3", name: "Owen Carter", depot: "north" },
  { id: "d4", name: "Lucia Romero", depot: "river" },
  { id: "d5", name: "Ben Adeyemi", depot: "river" },
  { id: "d6", name: "Hanna Berg", depot: "east" },
  { id: "d7", name: "Yusuf Demir", depot: "east" },
  { id: "d8", name: "Clara Wendt", depot: "east" },
] as const;

const CUSTOMERS = [
  "Holloway Garden Supplies",
  "Marlow & Finch Books",
  "Oakridge Pharmacy",
  "Brixley Cycles",
  "Tamsin's Bakery",
  "Northfold Office",
  "Pellham Hardware",
  "Greywick Studio",
  "Juniper Lane Florist",
  "Ashcombe Dental",
] as const;

interface Stop {
  id: string;
  shipment: string;
  customer: string;
  /** The window the customer was promised, `[from, to]`. */
  window: readonly [number, number];
  /** When the tour plans to be there. */
  arrival: number;
}

interface Tour {
  id: string;
  vehicle: string;
  driver: string;
  depot: string;
  /** Leaves the depot, loaded; back at the depot. */
  from: number;
  to: number;
  /** Minutes of loading before `from`. */
  loading: number;
  km: number;
  stops: readonly Stop[];
}

/** One tour per vehicle. Stops every half hour or so, each with a two-hour
    window; every seventh arrival misses its window - the late ones a
    dispatcher looks for. */
function tour(index: number): Tour {
  const r = random(700 + index);
  const vehicle = VEHICLES[index]!;
  const truck = vehicle.type === "truck";
  const from = at(truck ? 6 : 7, index % 2 === 0 ? 0 : 30);
  const count = truck ? 5 : 8 + Math.floor(r() * 4);
  const stops: Stop[] = [];
  let arrival = from + (20 + Math.floor(r() * 15)) * MINUTE;
  for (let i = 0; i < count; i++) {
    const n = index * 20 + i;
    const slot = Math.floor((arrival - at(0)) / (2 * 60 * MINUTE)) * 2;
    /* A late stop's window closed half an hour or more before the arrival. */
    const late = i >= 2 && n % 7 === 3;
    const halfHour = 30 * MINUTE;
    const windowFrom = late ? Math.floor(arrival / halfHour) * halfHour - 5 * halfHour : at(slot);
    stops.push({
      id: `s-${index + 1}-${i + 1}`,
      shipment: `FP-${(1_004_210 + n * 13).toString()}`,
      customer: CUSTOMERS[(n * 3) % CUSTOMERS.length]!,
      window: [windowFrom, windowFrom + 2 * 60 * MINUTE],
      arrival,
    });
    arrival += (truck ? 45 : 22) * MINUTE + Math.floor(r() * 18) * MINUTE;
  }
  return {
    id: `T-${String(index + 1).padStart(2, "0")}`,
    vehicle: vehicle.id,
    driver: DRIVERS[index]!.id,
    depot: vehicle.depot,
    from,
    to: arrival + 30 * MINUTE,
    loading: truck ? 45 : 30,
    km: Math.round((truck ? 140 : 70) + r() * 40),
    stops,
  };
}

const TOURS: readonly Tour[] = VEHICLES.map((_, index) => tour(index));

interface Shipment {
  id: string;
  customer: string;
  /** In kg. */
  weight: number;
  status: "delivered" | "out for delivery" | "failed attempt";
  window: readonly [number, number];
  tour: string;
}

/** One shipment per stop. Before now, delivered - or a failed attempt, where
    nobody was in; after now, out for delivery. */
const SHIPMENTS: readonly Shipment[] = TOURS.flatMap((one) =>
  one.stops.map((stop) => {
    const n = Number(stop.shipment.slice(3));
    return {
      id: stop.shipment,
      customer: stop.customer,
      weight: Math.round((VEHICLES.find((v) => v.id === one.vehicle)!.type === "truck" ? 180 : 2) + ((n * 7919) % 230) / 10),
      status: stop.arrival > NOW ? "out for delivery" : n % 11 === 0 ? "failed attempt" : "delivered",
      window: stop.window,
      tour: one.id,
    } satisfies Shipment;
  }),
);

const tours = rowFilter({
  id: "tours",
  label: "Tours",
  matches: (shipment: Shipment, chosen: readonly string[]) => chosen.includes(shipment.tour),
});

const TOUR_OPTIONS = [...new Set(SHIPMENTS.map((s) => s.tour))].map((tour) => ({ value: tour, label: tour }));

export default function ALargerToolbar() {
  const t = useTable(SHIPMENTS, { rowKey: (s) => s.id, rowFilters: [tours] });
  const { Table, Column } = t;

  return (
    <Table ariaLabel="Shipments">
      <Toolbar size="md">
        <Search placeholder="Shipment or customer" />
        <MultiSelect
          aria-label="Tours"
          placeholder="All tours"
          options={TOUR_OPTIONS}
          value={t.conditionOf(tours) ?? []}
          onChange={(next) => t.setFilter(tours, next.length > 0 ? next : null)}
        />
        <ColumnMenu />
        <Export />
      </Toolbar>
      <Column value="id" label="Shipment" rowHeader />
      <Column value="customer" label="Customer" />
      <Column value="tour" label="Tour" />
      <Column value="weight" label="Weight (kg)" />
      <Pagination />
    </Table>
  );
}
```

### A dispatcher's bar

Three controls of one's own and the table's parts in one `md` bar, a quick filter beside the table - and every condition counted, reset and kept in the view alike. Only the one set from outside describes itself as a chip: the others show in their control.

```tsx
import { Button, Combobox, MultiSelect, Select, Stack } from "@umriss-ui/core";
import { ColumnMenu, Export, Pagination, Search, Toolbar, rowFilter, useTable } from "@umriss-ui/table";

/* Data from the logistics world, written out here so the example runs on its own. */
/** A small LCG - the same numbers on every computer. */
function random(seed: number): () => number {
  let s = seed >>> 0;
  return () => {
    s = (Math.imul(s, 1664525) + 1013904223) >>> 0;
    return s / 4294967296;
  };
}

const at = (hours: number, minutes = 0) => new Date(2026, 2, 17, hours, minutes).getTime();
const MINUTE = 60_000;

/** Tuesday, 17 March 2026, 10:30 - the moment the screens are read at. */
const NOW = at(10, 30);

interface Vehicle {
  id: string;
  plate: string;
  type: "van" | "e-van" | "truck";
  /** Payload, in kg. */
  capacity: number;
  depot: string;
}

const VEHICLES: readonly Vehicle[] = [
  { id: "v1", plate: "FP 214 K", type: "van", capacity: 1200, depot: "north" },
  { id: "v2", plate: "FP 377 K", type: "e-van", capacity: 900, depot: "north" },
  { id: "v3", plate: "FP 118 R", type: "truck", capacity: 7500, depot: "north" },
  { id: "v4", plate: "FP 402 R", type: "van", capacity: 1200, depot: "river" },
  { id: "v5", plate: "FP 455 R", type: "e-van", capacity: 900, depot: "river" },
  { id: "v6", plate: "FP 290 E", type: "van", capacity: 1200, depot: "east" },
  { id: "v7", plate: "FP 311 E", type: "e-van", capacity: 900, depot: "east" },
  { id: "v8", plate: "FP 520 E", type: "truck", capacity: 7500, depot: "east" },
];

const DRIVERS = [
  { id: "d1", name: "Martin Hale", depot: "north" },
  { id: "d2", name: "Nadia Petrova", depot: "north" },
  { id: "d3", name: "Owen Carter", depot: "north" },
  { id: "d4", name: "Lucia Romero", depot: "river" },
  { id: "d5", name: "Ben Adeyemi", depot: "river" },
  { id: "d6", name: "Hanna Berg", depot: "east" },
  { id: "d7", name: "Yusuf Demir", depot: "east" },
  { id: "d8", name: "Clara Wendt", depot: "east" },
] as const;

const CUSTOMERS = [
  "Holloway Garden Supplies",
  "Marlow & Finch Books",
  "Oakridge Pharmacy",
  "Brixley Cycles",
  "Tamsin's Bakery",
  "Northfold Office",
  "Pellham Hardware",
  "Greywick Studio",
  "Juniper Lane Florist",
  "Ashcombe Dental",
] as const;

interface Stop {
  id: string;
  shipment: string;
  customer: string;
  /** The window the customer was promised, `[from, to]`. */
  window: readonly [number, number];
  /** When the tour plans to be there. */
  arrival: number;
}

interface Tour {
  id: string;
  vehicle: string;
  driver: string;
  depot: string;
  /** Leaves the depot, loaded; back at the depot. */
  from: number;
  to: number;
  /** Minutes of loading before `from`. */
  loading: number;
  km: number;
  stops: readonly Stop[];
}

/** One tour per vehicle. Stops every half hour or so, each with a two-hour
    window; every seventh arrival misses its window - the late ones a
    dispatcher looks for. */
function tour(index: number): Tour {
  const r = random(700 + index);
  const vehicle = VEHICLES[index]!;
  const truck = vehicle.type === "truck";
  const from = at(truck ? 6 : 7, index % 2 === 0 ? 0 : 30);
  const count = truck ? 5 : 8 + Math.floor(r() * 4);
  const stops: Stop[] = [];
  let arrival = from + (20 + Math.floor(r() * 15)) * MINUTE;
  for (let i = 0; i < count; i++) {
    const n = index * 20 + i;
    const slot = Math.floor((arrival - at(0)) / (2 * 60 * MINUTE)) * 2;
    /* A late stop's window closed half an hour or more before the arrival. */
    const late = i >= 2 && n % 7 === 3;
    const halfHour = 30 * MINUTE;
    const windowFrom = late ? Math.floor(arrival / halfHour) * halfHour - 5 * halfHour : at(slot);
    stops.push({
      id: `s-${index + 1}-${i + 1}`,
      shipment: `FP-${(1_004_210 + n * 13).toString()}`,
      customer: CUSTOMERS[(n * 3) % CUSTOMERS.length]!,
      window: [windowFrom, windowFrom + 2 * 60 * MINUTE],
      arrival,
    });
    arrival += (truck ? 45 : 22) * MINUTE + Math.floor(r() * 18) * MINUTE;
  }
  return {
    id: `T-${String(index + 1).padStart(2, "0")}`,
    vehicle: vehicle.id,
    driver: DRIVERS[index]!.id,
    depot: vehicle.depot,
    from,
    to: arrival + 30 * MINUTE,
    loading: truck ? 45 : 30,
    km: Math.round((truck ? 140 : 70) + r() * 40),
    stops,
  };
}

const TOURS: readonly Tour[] = VEHICLES.map((_, index) => tour(index));

interface Shipment {
  id: string;
  customer: string;
  /** In kg. */
  weight: number;
  status: "delivered" | "out for delivery" | "failed attempt";
  window: readonly [number, number];
  tour: string;
}

/** One shipment per stop. Before now, delivered - or a failed attempt, where
    nobody was in; after now, out for delivery. */
const SHIPMENTS: readonly Shipment[] = TOURS.flatMap((one) =>
  one.stops.map((stop) => {
    const n = Number(stop.shipment.slice(3));
    return {
      id: stop.shipment,
      customer: stop.customer,
      weight: Math.round((VEHICLES.find((v) => v.id === one.vehicle)!.type === "truck" ? 180 : 2) + ((n * 7919) % 230) / 10),
      status: stop.arrival > NOW ? "out for delivery" : n % 11 === 0 ? "failed attempt" : "delivered",
      window: stop.window,
      tour: one.id,
    } satisfies Shipment;
  }),
);

type Status = Shipment["status"];

const tours = rowFilter({
  id: "tours",
  label: "Tours",
  matches: (shipment: Shipment, chosen: readonly string[]) => chosen.includes(shipment.tour),
});

const status = rowFilter({
  id: "status",
  label: "Status",
  matches: (shipment: Shipment, wanted: Status) => shipment.status === wanted,
});

const customer = rowFilter({
  id: "customer",
  label: "Customer",
  matches: (shipment: Shipment, name: string) => shipment.customer === name,
});

/* Set from a button beside the table, not from a control in the bar - so it
   says itself in the toolbar, where its cross lifts it. */
const heavy = rowFilter({
  id: "heavy",
  label: "Weight",
  matches: (shipment: Shipment, atLeast: number) => shipment.weight >= atLeast,
  describe: (atLeast) => `from ${atLeast} kg`,
});

const TOUR_OPTIONS = [...new Set(SHIPMENTS.map((s) => s.tour))].map((tour) => ({ value: tour, label: tour }));
const CUSTOMER_OPTIONS = [...new Set(SHIPMENTS.map((s) => s.customer))]
  .sort()
  .map((name) => ({ value: name, label: name }));

export default function ADispatchersBar() {
  const t = useTable(SHIPMENTS, {
    rowKey: (s) => s.id,
    rowFilters: [tours, status, customer, heavy],
    initialView: { conditions: { heavy: 20 } },
  });
  const { Table, Column } = t;

  return (
    <Stack gap={3}>
      <Stack direction="row" gap={2}>
        <Button size="sm" onClick={() => t.setFilter(heavy, 20)}>
          Heavy parcels
        </Button>
      </Stack>

      <Table ariaLabel="Shipments of the day">
        <Toolbar size="md">
          <Search placeholder="Shipment" />
          <MultiSelect
            aria-label="Tours"
            placeholder="All tours"
            options={TOUR_OPTIONS}
            value={t.conditionOf(tours) ?? []}
            onChange={(next) => t.setFilter(tours, next.length > 0 ? next : null)}
          />
          <Select
            aria-label="Status"
            value={t.conditionOf(status) ?? ""}
            onChange={(event) => t.setFilter(status, event.target.value === "" ? null : (event.target.value as Status))}
          >
            <option value="">Every status</option>
            <option value="out for delivery">Out for delivery</option>
            <option value="failed attempt">Failed attempt</option>
            <option value="delivered">Delivered</option>
          </Select>
          <Combobox
            aria-label="Customer"
            placeholder="Any customer"
            options={CUSTOMER_OPTIONS}
            value={t.conditionOf(customer)}
            onChange={(name) => t.setFilter(customer, name)}
          />
          <ColumnMenu />
          <Export />
        </Toolbar>
        <Column value="id" label="Shipment" rowHeader />
        <Column value="customer" label="Customer" />
        <Column value="tour" label="Tour" />
        <Column value="status" label="Status" />
        <Column value="weight" label="Weight (kg)" />
        <Pagination />
      </Table>
    </Stack>
  );
}
```

## When to use something else

- A filter on a column the table offers itself, in its header → Filter

## 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), [ColumnMenu](https://romanhaendler.github.io/umriss-ui/table/columnmenu/#keyboard-columnmenu), [Select](https://romanhaendler.github.io/umriss-ui/core/select/#keyboard-select), [MultiSelect](https://romanhaendler.github.io/umriss-ui/core/multiselect/#keyboard-multiselect), [Combobox](https://romanhaendler.github.io/umriss-ui/core/combobox/#keyboard-combobox) and [Button](https://romanhaendler.github.io/umriss-ui/core/button/#keyboard-button) apply here.

## API

### `ToolbarProps`

| Prop | Type | Default | Description |
|---|---|---|---|
| `children` | `ReactNode` | — | Search, column menu, export – on the left in the table toolbar, before the conditions.<br>Shown in: [Controls of your own at the toolbar's size](https://romanhaendler.github.io/umriss-ui/table/toolbar-controls/#controls-at-the-toolbars-size), [A larger toolbar](https://romanhaendler.github.io/umriss-ui/table/toolbar-controls/#a-larger-toolbar), [A dispatcher's bar](https://romanhaendler.github.io/umriss-ui/table/toolbar-controls/#a-dispatcher-s-bar) and 37 more |
| `size` | [`ToolbarSize`](https://romanhaendler.github.io/umriss-ui/table/toolbar-controls/#type-ToolbarSize)<br>`"sm" \| "md"` | `"sm"` | The size of every control the table puts into it - search, column menu, export, "reset", the bulk actions -: `sm` or `md`. A control of one's own takes the same size by its own prop.<br>Shown in: [A larger toolbar](https://romanhaendler.github.io/umriss-ui/table/toolbar-controls/#a-larger-toolbar), [A dispatcher's bar](https://romanhaendler.github.io/umriss-ui/table/toolbar-controls/#a-dispatcher-s-bar) |
| `of` | [`TableRef`](https://romanhaendler.github.io/umriss-ui/table/toolbar-controls/#type-TableRef) | — | The table, when the table toolbar does not stand inside it. If it then stands behind the table in the tree, it registers only after the first frame – until then a table with a search or a column filter shows its own toolbar. Placed before the table, there is no such change.<br>Shown in: [Place it outside the table (Toolbar)](https://romanhaendler.github.io/umriss-ui/table/toolbar/#place-it-outside), [Review cost centres against budget (Scenarios)](https://romanhaendler.github.io/umriss-ui/table/#review-cost-centres) |

#### Styling

| Prop | Type | Default | Description |
|---|---|---|---|
| `className` | `string` | — | Classes on the table toolbar. |

### `RowFilter<Z, B>`

| Prop | Type | Default | Description |
|---|---|---|---|
| `id` *required* | `string` | — | Its name in `t.view.conditions` and in what server mode reports - the one place the filter is spelled. Not the id of a column of the same table.<br>Shown in: [Controls of your own at the toolbar's size](https://romanhaendler.github.io/umriss-ui/table/toolbar-controls/#controls-at-the-toolbars-size), [A larger toolbar](https://romanhaendler.github.io/umriss-ui/table/toolbar-controls/#a-larger-toolbar), [A dispatcher's bar](https://romanhaendler.github.io/umriss-ui/table/toolbar-controls/#a-dispatcher-s-bar) and 3 more |
| `label` *required* | `string` | — | What it is called: before the text of its chip, and in the chip's accessible names.<br>Shown in: [Controls of your own at the toolbar's size](https://romanhaendler.github.io/umriss-ui/table/toolbar-controls/#controls-at-the-toolbars-size), [A larger toolbar](https://romanhaendler.github.io/umriss-ui/table/toolbar-controls/#a-larger-toolbar), [A dispatcher's bar](https://romanhaendler.github.io/umriss-ui/table/toolbar-controls/#a-dispatcher-s-bar) and 3 more |
| `matches` *required* | `(row: Z, condition: B) => boolean` | — | Whether the row satisfies the condition. Any question over the whole row; whatever varies belongs in the condition - the table cannot see a change in a closure.<br>Shown in: [Controls of your own at the toolbar's size](https://romanhaendler.github.io/umriss-ui/table/toolbar-controls/#controls-at-the-toolbars-size), [A larger toolbar](https://romanhaendler.github.io/umriss-ui/table/toolbar-controls/#a-larger-toolbar), [A dispatcher's bar](https://romanhaendler.github.io/umriss-ui/table/toolbar-controls/#a-dispatcher-s-bar) and 3 more |
| `describe` | `(condition: B) => string` | — | The text of the condition in the table toolbar, behind the label. Without it the condition has no chip: the application's control shows it.<br>Shown in: [A dispatcher's bar](https://romanhaendler.github.io/umriss-ui/table/toolbar-controls/#a-dispatcher-s-bar), [Ask a question over several fields (Filter)](https://romanhaendler.github.io/umriss-ui/table/filter/#a-question-over-several-fields) |

### Types on this page

#### `ToolbarSize`

The size of a toolbar's controls.

```ts
type ToolbarSize = "sm" | "md";
```

#### `TableRef`

What a part without a row type needs from a table – for `of`.

```ts
type TableRef = Omit<TableSnapshot<unknown>, "setFilter" | "conditionOf"> & {
  readonly Table: unknown;
  /** Without a row kind: any id, any condition. */
  setFilter(column: string, condition: unknown): void;
};
```
