# FitToolbar

> A toolbar whose items give way in named steps (label to icon, into More, a group to one button, a group to the next line, the primary action to +) as the region it sits in narrows, measured rather than guessed.

Source: https://raft-design.botiverse.build/components/fit-toolbar. Generated from the current release; where it differs from what you remember, this page is right.

`FitToolbar` is a toolbar that keeps one row as its region narrows by letting its items give way, one step at a time, in an order they declare. It is the shared engine of [When a row runs out of room](https://raft-design.botiverse.build/patterns/responsive.md#when-a-row-runs-out-of-room): [SelectionToolbar](https://raft-design.botiverse.build/components/selection-toolbar.md) is built on it, and the list toolbars of Tasks, Computers, Members and search will use it (#proj-raft-ui:#225). It measures its content in the width of its own region, never the window's: a task list can be 320px or 512px wide on the same screen. So its parent must have a definite width (a column, a panel, a block that fills its row), not one sized by the toolbar: a parent that shrinks to fit the toolbar always has room for it.

```tsx
import { useCallback, useState } from "react";
import type { MouseEvent } from "react";
import { CircleDashed, CircleDot, CircleCheck, Kanban, List, Plus, UserRound } from "lucide-react";
import {
  SegmentedControl,
  SegmentedControlItem,
  SegmentedControlLabel,
  Text,
} from "@botiverse/raft-ui";
import { FitToolbar, FitToolbarButton, FitToolbarGroup, useFitItem } from "@botiverse/raft-ui/wip";

/** The view switch: its labels are the toolbar's `label` step. */
function ViewSwitch({
  view,
  onViewChange,
}: {
  view: string;
  onViewChange: (view: string) => void;
}) {
  const item = useFitItem({ label: 30 });
  return (
    <SegmentedControl aria-label="View" value={view} onValueChange={onViewChange} {...item.props}>
      <SegmentedControlItem value="list">
        <List />
        <SegmentedControlLabel>List</SegmentedControlLabel>
      </SegmentedControlItem>
      <SegmentedControlItem value="board">
        <Kanban />
        <SegmentedControlLabel>Board</SegmentedControlLabel>
      </SegmentedControlItem>
    </SegmentedControl>
  );
}

export function FitToolbarContent() {
  const [view, setView] = useState("list");
  const [filter, setFilter] = useState("in-progress");
  const [last, setLast] = useState("");
  const choose = useCallback((event: MouseEvent<HTMLElement>) => {
    setFilter(event.currentTarget.dataset.value ?? "");
  }, []);
  const create = useCallback(() => setLast("New task"), []);

  return (
    <div className="flex w-full flex-col gap-3">
      <div className="w-full max-w-lg min-w-60 resize-x overflow-hidden rounded-container border border-line-seam p-3">
        <FitToolbar aria-label="Tasks" wrap className="gap-2">
          <FitToolbarGroup firstLine className="gap-2">
            <ViewSwitch view={view} onViewChange={setView} />
          </FitToolbarGroup>
          <FitToolbarGroup overflow="scroll" className="gap-2">
            <FitToolbarButton
              variant="outline"
              icon={<CircleDashed />}
              data-value="to-do"
              aria-pressed={filter === "to-do"}
              data-active={filter === "to-do" ? "" : undefined}
              onClick={choose}
            >
              To do
            </FitToolbarButton>
            <FitToolbarButton
              variant="outline"
              icon={<CircleDot />}
              data-value="in-progress"
              aria-pressed={filter === "in-progress"}
              data-active={filter === "in-progress" ? "" : undefined}
              onClick={choose}
            >
              In progress
            </FitToolbarButton>
            <FitToolbarButton
              variant="outline"
              icon={<CircleCheck />}
              data-value="done"
              aria-pressed={filter === "done"}
              data-active={filter === "done" ? "" : undefined}
              onClick={choose}
            >
              Done
            </FitToolbarButton>
            <FitToolbarButton
              variant="outline"
              icon={<UserRound />}
              data-value="mine"
              aria-pressed={filter === "mine"}
              data-active={filter === "mine" ? "" : undefined}
              onClick={choose}
            >
              Assigned to me
            </FitToolbarButton>
          </FitToolbarGroup>
          <FitToolbarGroup firstLine className="gap-2">
            <FitToolbarButton variant="primary" icon={<Plus />} labelOrder={20} onClick={create}>
              New task
            </FitToolbarButton>
          </FitToolbarGroup>
        </FitToolbar>
      </div>
      <Text.Sans color="muted" size="small" role="status">
        {view === "list" ? "List" : "Board"} · {filter}
        {last ? ` · ${last}` : ""}
      </Text.Sans>
    </div>
  );
}

export function FitToolbarComposition() {
  return (
    <div className="flex size-full items-start justify-center p-4">
      <FitToolbarContent />
    </div>
  );
}
```

## When to use

| Situation                                                          | Use                                                                                          |
| ------------------------------------------------------------------ | -------------------------------------------------------------------------------------------- |
| A toolbar whose labels, groups or actions may not fit its region   | FitToolbar                                                                                   |
| A row of segments that turn into icons when their labels don't fit | [SegmentedControl](https://raft-design.botiverse.build/components/segmented-control.md) with `iconsOnlyBelow="auto"`   |
| The toolbar for selected items                                     | [SelectionToolbar](https://raft-design.botiverse.build/components/selection-toolbar.md), a FitToolbar that never wraps |

## Anatomy

- `FitToolbar`: the toolbar, a Base UI Toolbar: one Tab stop, the arrow keys inside, skipping what is hidden. `wrap` lets its groups move to lines of their own.
- `FitToolbarGroup`: a group of items in one row, a fixed gap apart. `firstLine` keeps it on the first line; `wrapOrder` says when it wraps; `wrapLine` names the line it wraps onto; `collapsed` is the group as one button (a Filter button for its filters), shown from `collapseOrder` on. `overflow="scroll"` makes it scroll sideways instead (see Behavior).
- `useFitItem(steps)`: makes an element an item; spread its `props` on it. `steps` gives each step the item can take its order: `{ label: 30, more: 40 }`. `moreItem` is what the item is in More's menu. `taken(step)` says which steps it has taken, for what it renders (a tooltip once its label is gone).
- `FitToolbarButton`: a toolbar button that can give way: an `md` Button with an icon and a label, an icon square with its label as a tooltip from `labelOrder` on, a + square with its label as a tooltip from `plusOrder` on, in More from `moreOrder` on. `collapsedIcon` is an icon shown only once the label has given way, in place of `icon`: a button that reads as text while it has room (Clear filters) and as an icon (×) once it hasn't. A button with neither an icon nor a `collapsedIcon` refuses its `label` step, since nothing would be left to show.
- `FitToolbarPicker`: a picker's trigger as one toolbar item (a filter: Assignee, Status): a [PickerTriggerButton](https://raft-design.botiverse.build/components/picker-trigger-button.md) with an icon, a label, what is chosen (`value`) and a chevron. Pass the popup's trigger as `trigger` (`<DropdownMenuTrigger />`, `<PopoverTrigger />`); the picker renders it as the toolbar button. With a value it fills `info-soft` by itself, as an applied filter does ([Select › Filters](https://raft-design.botiverse.build/components/select.md#filters)), so the page styles nothing. A count (a number, or a string of digits) is set in Space Mono; any other value (In progress, Kirby) keeps the label's font, since Space Mono is for what machines write ([Typography](https://raft-design.botiverse.build/foundations/typography.md)). From `labelOrder` on its label gives way and the icon, what is chosen and the chevron stay (# ⌄, 👤 1 ⌄): the chevron says it opens a list, as on every PickerTriggerButton. Its accessible name is the label, then what is chosen ("Assignee 1"), and the label shows as its tooltip.
- `useFitMore()`: makes an element the toolbar's More; spread its `props` on it. Hide it until it carries `data-fit-shown`.
- `FitToolbarMoreItems`: the items in More, in their order: put it in More's menu.
- `useInFitToolbar()`: true inside a FitToolbar, for a control that would otherwise measure its own fit.

## Behavior

- The steps, each written on the item as `data-fit-<step>` for its styles:
  - `label`: its label hides and its icon stays, the label kept as its accessible name and its tooltip.
  - `more`: it moves into More (`data-fit-more`; hide it).
  - `collapse`: a group shows its one button instead of its items.
  - `wrap`: a group moves to a line of its own, only with `wrap`.
  - `plus`: a primary action shows as + alone (`FitToolbarButton`'s `plusOrder`), its icon and label gone, the label kept as its accessible name and its tooltip.
- The steps of every item go into one order for the whole toolbar: by the number each item gives them, the lower first; at the same number the last item first, and one item's steps in the order above (`label`, `more`, `collapse`, `wrap`, `plus`). A step's name says nothing about when it comes: the numbers do. A list toolbar gives its numbers in this order: the filters' labels, then the groups' `wrap`, then the primary's `plus` (say 10, 20, 30). After each step the toolbar measures again, and stops at the first that fits. It starts from nothing taken on every measure, so the outcome depends on the width alone and doesn't flicker. It measures after every render, whenever its region changes size, whenever its content changes on its own (a label that becomes "Copied", a count, an item inside a child that rendered alone) and when a font arrives.
- Items of the same kind in one group change form at the same time: the same `label` (or `plus`) step at the same number, in one `FitToolbarGroup`, is one step for all of its items, taken and given back together. A group already says its items are one kind, and a row of filters half icons and half labels reads as broken: at 780px Tasks showed three filters as icons beside Assignee with its label (sindresorhus; Jasper's call, 2026-11-10). Give an item another number to let it go on its own.
- It wraps only between groups, from the end. Groups with the same `wrapLine` wrap onto the same line, in their order, the toolbar's gap apart; a group without one takes a line of its own. Each line starts at the toolbar's start, the toolbar's gap below the one before, and its groups are never spread across it: a gap that grows with the width would be one more distance to read. The page decides which groups stay on the first line (`firstLine`), because it knows which controls people reach for first.
- Once a group has wrapped, what its items gave before it wrapped is given back, the last first, wherever the toolbar still fits without it: filters that wrapped to the second line get their labels back, and so does a New task button there. The toolbar measures from nothing taken every time, so the result depends on the width alone: the same width gives the same toolbar whether it is narrowing or widening, and it never flickers between two.
- A group with `overflow="scroll"` takes the rest of its line and scrolls sideways instead of giving way, its items whole, in a horizontal [ScrollArea](https://raft-design.botiverse.build/components/scroll-area.md) (a small scrollbar only while it scrolls, no tab stop of its own), the side that hides more fading (`mask-fade-x`, [Edges fade](https://raft-design.botiverse.build/foundations/spacing.md#edges-fade)). A vertical wheel scrolls it, and an item that takes focus, by Tab or the arrow keys, comes into view clear of the fade. It gives way itself only when it can't show one whole item. stdrc chose it for a phone's filters (2026-11-10): a row of filters that wraps pushes the list down, while one that scrolls keeps every filter one swipe away. It works as slock web's FilterStrip does, so the web can move to it. Whether narrow lists on a desktop scroll too is still open, so it is a group's choice, with no width built in.
- An item the person applied or chose in a scrolling group (an active filter) is always in view: mark it `data-active`, and the group scrolls it into view when it mounts and whenever it becomes active, with no animation. A filter that narrows the list must never sit hidden off the edge. Put applied filters first as well, so they are in view before any scrolling.
- A [SegmentedControl](https://raft-design.botiverse.build/components/segmented-control.md) in the toolbar follows it: give it a `label` step (spread `useFitItem({ label })`'s props on it) and it shows only its icons while that step is taken. Its own `iconsOnlyBelow` is off there (`useInFitToolbar`), because two measures of the same room would disagree: the toolbar knows what else gives way first. A control of text alone (Mine and All with their counts) refuses the step and keeps its labels: with no icons there would be nothing left to show.
- A scrolling group scrolls only as its last state. While it can still wrap and hasn't, it counts as not fitting whenever its items overflow it, so the steps before its `wrap` (the filters' labels) come first, then the wrap, and only a group on a line of its own scrolls. Before, at 780px the filters scrolled on the first line with their labels kept, the last ones hidden past the fade, where giving the labels fit them all (sindresorhus, 2026-11-10). A group that can't wrap (`firstLine`, or a toolbar without `wrap`) scrolls as before.
- A scrolling group stays where the person scrolled it. Each measure starts from nothing taken, which can shrink its content for a moment (labels given before the wrap) and pull its scroll back; the toolbar keeps the scroll position across the measure. The active item is scrolled into view only when it newly becomes active (`data-active` added, or an item added that carries it), and the group's first layouts keep it in view only until the person scrolls: a count changing, a measure or a resize after that leaves the group where they put it. Before, a count that changed on its own scrolled the filters back to the active one under the person's finger (risk 3 of sindresorhus' review, with Jasper, 2026-11-10).
- A toolbar that floats over content leaves `wrap` off, because a second line would cover what it floats over: its last step is into More ([SelectionToolbar](https://raft-design.botiverse.build/components/selection-toolbar.md#behavior)).
- The primary action becomes + only after the groups have wrapped: give its `plus` a higher number than any `wrap`. It is the last step, because a + is the least clear form of the action people came for, and wrapping costs only a line. Tasks at 375px and 420px needs it (sindresorhus, #proj-raft-ui:#225): once the filters have wrapped to their own scrolling line, the first line (Mine and All with their counts, sort, list or board, Create task) fits only with Create task as +. A `plus` taken before a wrap would not be given back after it, since it is on the first line.
- Fits means nothing pokes out of the toolbar or out of any of its groups, and no line has broken by itself. Its items don't shrink (`shrink-0` groups): a control is never squeezed below its own content.
- It measures only what is laid out in the row: it looks through `display: contents` and leaves out what is hidden, out of flow (`position: absolute` or `fixed`) or of no size. A popover, menu or tooltip opened from a toolbar item puts focus guards beside its trigger, fixed at the window's corner, and they are not part of the row: counted, opening a popup made the toolbar give way. Items on one line may differ in height (a primary Button beside 32px filters); a line has broken only where an item sits wholly below or above the first on it.

## Composition

The Tasks toolbar: Mine and All (text alone, so they keep their labels), list or board and Create task stay on the first line; the filters (pickers with what is chosen, and Clear filters as × once it gives way) scroll, give their labels, then wrap to a line of their own (20) before Create task becomes + (30). Narrow the box to watch the steps.

```tsx
import { useCallback, useState } from "react";
import type { ReactNode } from "react";
import { CircleDot, Kanban, List, Plus, UserRound, X } from "lucide-react";
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItemLabel,
  DropdownMenuRadioGroup,
  DropdownMenuRadioItem,
  DropdownMenuTrigger,
  SegmentedControl,
  SegmentedControlCount,
  SegmentedControlItem,
  SegmentedControlLabel,
  Text,
} from "@botiverse/raft-ui";
import {
  FitToolbar,
  FitToolbarButton,
  FitToolbarGroup,
  FitToolbarPicker,
  useFitItem,
} from "@botiverse/raft-ui/wip";

/** Mine and All, text alone with their counts: they refuse the `label` step and keep their text. */
function Scope({
  scope,
  onScopeChange,
}: {
  scope: string;
  onScopeChange: (scope: string) => void;
}) {
  const item = useFitItem({ label: 10 });
  return (
    <SegmentedControl
      aria-label="Scope"
      value={scope}
      onValueChange={onScopeChange}
      {...item.props}
    >
      <SegmentedControlItem value="mine">
        <SegmentedControlLabel>Mine</SegmentedControlLabel>
        <SegmentedControlCount>61</SegmentedControlCount>
      </SegmentedControlItem>
      <SegmentedControlItem value="all">
        <SegmentedControlLabel>All</SegmentedControlLabel>
        <SegmentedControlCount>120</SegmentedControlCount>
      </SegmentedControlItem>
    </SegmentedControl>
  );
}

/** List or board: icons, so it gives its labels before the filters wrap. */
function ViewSwitch({
  view,
  onViewChange,
}: {
  view: string;
  onViewChange: (view: string) => void;
}) {
  const item = useFitItem({ label: 10 });
  return (
    <SegmentedControl aria-label="View" value={view} onValueChange={onViewChange} {...item.props}>
      <SegmentedControlItem value="list">
        <List />
        <SegmentedControlLabel>List</SegmentedControlLabel>
      </SegmentedControlItem>
      <SegmentedControlItem value="board">
        <Kanban />
        <SegmentedControlLabel>Board</SegmentedControlLabel>
      </SegmentedControlItem>
    </SegmentedControl>
  );
}

/** A filter as one toolbar item: its label gives way, what is chosen stays. */
function Filter({
  label,
  icon,
  options,
  value,
  onValueChange,
}: {
  label: string;
  icon: ReactNode;
  options: readonly string[];
  value: string;
  onValueChange: (value: string) => void;
}) {
  return (
    <DropdownMenu>
      <FitToolbarPicker
        trigger={<DropdownMenuTrigger />}
        icon={icon}
        value={value === "Any" ? undefined : value}
        labelOrder={10}
        data-active={value === "Any" ? undefined : ""}
      >
        {label}
      </FitToolbarPicker>
      <DropdownMenuContent aria-label={label}>
        <DropdownMenuRadioGroup value={value} onValueChange={onValueChange}>
          {options.map((option) => (
            <DropdownMenuRadioItem key={option} value={option}>
              <DropdownMenuItemLabel>{option}</DropdownMenuItemLabel>
            </DropdownMenuRadioItem>
          ))}
        </DropdownMenuRadioGroup>
      </DropdownMenuContent>
    </DropdownMenu>
  );
}

const STATUSES = ["Any", "To do", "In progress", "Done"] as const;
const ASSIGNEES = ["Any", "Me", "Kirby", "Jasper"] as const;

/**
 * The Tasks toolbar: Mine and All, list or board and Create task on the first
 * line; the filters scroll, and wrap to a line of their own (20) before
 * Create task becomes + (30). Narrow the box to watch the steps.
 */
export function FitToolbarTasksContent() {
  const [scope, setScope] = useState("mine");
  const [view, setView] = useState("list");
  const [assignee, setAssignee] = useState("Any");
  const [status, setStatus] = useState("In progress");
  const [created, setCreated] = useState(0);
  const filtered = assignee !== "Any" || status !== "Any";
  const clear = useCallback(() => {
    setAssignee("Any");
    setStatus("Any");
  }, []);
  const create = useCallback(() => setCreated((count) => count + 1), []);

  return (
    <div className="flex w-full flex-col gap-3">
      <div className="w-full max-w-3xl min-w-60 resize-x overflow-hidden rounded-container border border-line-seam p-3">
        <FitToolbar aria-label="Tasks" wrap className="gap-2">
          <FitToolbarGroup firstLine className="gap-2">
            <Scope scope={scope} onScopeChange={setScope} />
          </FitToolbarGroup>
          <FitToolbarGroup overflow="scroll" wrapOrder={20} className="gap-2">
            <Filter
              label="Status"
              icon={<CircleDot />}
              options={STATUSES}
              value={status}
              onValueChange={setStatus}
            />
            <Filter
              label="Assignee"
              icon={<UserRound />}
              options={ASSIGNEES}
              value={assignee}
              onValueChange={setAssignee}
            />
            {filtered ? (
              <FitToolbarButton
                variant="ghost"
                collapsedIcon={<X />}
                labelOrder={10}
                onClick={clear}
              >
                Clear filters
              </FitToolbarButton>
            ) : null}
          </FitToolbarGroup>
          <FitToolbarGroup firstLine className="gap-2">
            <ViewSwitch view={view} onViewChange={setView} />
            <FitToolbarButton variant="primary" icon={<Plus />} plusOrder={30} onClick={create}>
              Create task
            </FitToolbarButton>
          </FitToolbarGroup>
        </FitToolbar>
      </div>
      <Text.Sans color="muted" size="small" role="status">
        {scope === "mine" ? "Mine" : "All"} · {view === "list" ? "List" : "Board"} · {status} ·{" "}
        {assignee}
        {created > 0 ? ` · ${created} created` : ""}
      </Text.Sans>
    </div>
  );
}

export function FitToolbarTasksComposition() {
  return (
    <div className="flex size-full items-start justify-center p-4">
      <FitToolbarTasksContent />
    </div>
  );
}
```

## Accessibility

- Give `FitToolbar` an `aria-label`. The arrow keys skip what has moved into More, and More's menu holds it.
- Focus follows the reading order, not the DOM's (WCAG 2.4.3, Focus Order): a wrapped group moves with CSS `order`, so it can sit later on screen than where it is in the DOM, and neither the browser's Tab order nor Base UI's toolbar follows `order`. The arrow keys go through the controls line by line as they show, in the DOM's order on each line, and Tab first enters the toolbar at the first control on screen, then wherever the person last was. A text field keeps the arrow keys while its caret can still move.
- The toolbar is one Tab stop, a SegmentedControl in it too: the control registers as one toolbar item, its segments reachable by Tab only while it holds the toolbar's stop, and the toolbar's arrow keys move through its segments without choosing one (Space chooses; Base UI's radio ignores Enter on purpose).
- One Tab stop for the whole toolbar holds only while every control in it is a Base UI Toolbar part (`Toolbar.Button`, `Toolbar.Link`, `Toolbar.Input`, or a trigger rendered through one, as `FitToolbarButton` and `FitToolbarPicker` are) or registers as one item the way SegmentedControl does. A plain `<button>` dropped in stays a Tab stop of its own, and the arrow keys still reach it in reading order.
- Up and Down choose nothing in a horizontal toolbar, on a segment too: a SegmentedControl's radio group would choose the next segment, while Left and Right only move between the toolbar's items.
- When the focused item moves into More (the region narrowed under it), focus goes to More, where the item now is, rather than falling to the page. Focus the person moved out of the toolbar stays where they put it.
- An item showing its icon alone keeps its label as its accessible name; show the label as a tooltip too.

## Open

- It stays in wip until sindresorhus confirms the step names and the API for the list toolbars of #225.
- Where the right-hand group (list or board, Create task) sits once the filters wrap, at the end of the first line or after the filters, waits on stdrc's choice between A, B and C (#proj-raft-ui:#225). Today it stays at the first line's end.
- Moving an item out of the toolbar, to a page's menu or another region, is not a give-way step: where an item lives is the page's layout decision, so the page renders it there itself. FitToolbar has no step for it.

## API

### FitToolbar

A toolbar whose items give way, in named steps and an order the items declare, as the region it sits in narrows: it measures its content in its own width, never the window's. The steps are written on each item as `data-fit-<step>` for its styles. A Base UI Toolbar: one Tab stop, the arrow keys inside.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `wrap` | `boolean` | `false` | Groups may move to a line of their own (the `wrap` step) when the row runs out of room. Off for a toolbar that floats over content: a second line would cover it, so its last step is into More. |
| `disabled` | `boolean` |  |  |
| `loopFocus` | `boolean` | `true` | If `true`, using keyboard navigation will wrap focus to the other end of the toolbar once the end is reached. |
| `orientation` | `BaseToolbar.Orientation` | `'horizontal'` | The orientation of the toolbar. |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### FitToolbarButton

A toolbar button that can give way: an `md` Button (ghost unless you say) with an icon and a label, an icon square with its label as a tooltip from `labelOrder` on, a + square with its label as a tooltip from `plusOrder` on, and hidden from `moreOrder` on.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `children` (required) | `ReactNode` |  | Its label: its accessible name and, once it has given way, its tooltip. |
| `collapsedIcon` | `ReactNode` |  | An icon shown only once its label has given way (the `label` step), in place of `icon`: a button that reads as text while it has room ("Clear filters") and as an icon (×) once it hasn't. |
| `icon` | `ReactNode` |  | Its icon, which stays when its label gives way. A button with a `label` step needs one (or a `collapsedIcon`). |
| `labelOrder` | `number` |  | When its label gives way (the `label` step). |
| `loading` | `boolean` |  | Busy: shows the spinner and blocks activation (click, Enter, Space and form submission) while keeping the button focusable. A busy button is `aria-disabled`, not natively `disabled`, so the focus a press left on it stays there and is still there when the wait ends. |
| `loadingLabel` | `string` |  | The spinner's accessible name while `loading`: say what is happening. |
| `moreItem` | `ReactNode` |  | What it is in More's menu. |
| `moreOrder` | `number` |  | When it moves into More (the `more` step). |
| `plusOrder` | `number` |  | When it shows as + alone, its icon and label gone (the `plus` step): for the primary action, after the groups have wrapped. |
| `variant` | `"primary" \| "accent" \| "warning" \| "danger" \| "default" \| "outline" \| "information" \| "muted" \| "success" \| "danger-secondary" \| "danger-outline" \| "ghost" \| "on-frame"` | `"ghost"` |  |
| `focusableWhenDisabled` | `boolean` | `false` | Whether the button should be focusable when disabled. |
| `nativeButton` | `boolean` | `true` | Whether the component renders a native `<button>` element when replacing it via the `render` prop. Set to `false` if the rendered element is not a button (for example, `<div>`). |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### FitToolbarGroup

A group of items in one row, a fixed gap apart. A toolbar wraps only between groups, the last first; groups with the same `wrapLine` share a line, any other takes one of its own.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `collapsed` | `ReactNode` |  | The group as one button (a Filter button for its filters), shown once it collapses. |
| `collapseOrder` | `number` |  | When it shows `collapsed` instead of its items (the `collapse` step). |
| `firstLine` | `boolean` | `false` | Stays on the first line: it never wraps. The page decides which groups do. |
| `overflow` | `"scroll"` |  | `scroll`: the group takes the rest of its line and scrolls sideways instead of giving way, its items whole, the side that hides more fading. Mark an applied or chosen item `data-active`: it is kept in view. |
| `wrapLine` | `string` |  | Groups with the same `wrapLine` wrap onto the same line, in their order, a fixed gap apart; a group without one takes a line of its own. |
| `wrapOrder` | `number` | `0` | When it moves to a line of its own (the `wrap` step), in a toolbar that allows it. |
| `disabled` | `boolean` | `false` | When `true` all toolbar items in the group are disabled. |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### FitToolbarPicker

A picker's trigger as one toolbar item (a filter: Assignee, Channel): a PickerTriggerButton with an icon, a label, what is chosen and a chevron, filled `info-soft` once a value is chosen, as an applied filter is (Select › Filters). From `labelOrder` on its label gives way, while its icon, what is chosen and the chevron stay ("# ⌄", "👤 1 ⌄"); its label stays its accessible name, read before the value ("Assignee 1"), and shows as its tooltip.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `children` (required) | `ReactNode` |  | Its label ("Assignee"): part of its accessible name and, once it has given way, its tooltip. |
| `icon` (required) | `ReactNode` |  | Its icon, which stays when its label gives way. |
| `labelOrder` | `number` |  | When its label gives way (the `label` step). |
| `moreItem` | `ReactNode` |  | What it is in More's menu. |
| `moreOrder` | `number` |  | When it moves into More (the `more` step). |
| `trigger` (required) | `ReactElement<{ render?: unknown; }, string \| JSXElementConstructor<any>>` |  | The picker's trigger, which opens its popup: `<PopoverTrigger />`, `<DropdownMenuTrigger />` or a Base UI `Menu.Trigger`. The picker renders it as a toolbar button, so the page nests nothing itself. |
| `value` | `(string \| number \| readonly string[]) & (ReactNode \| null)` |  | What is chosen: the value ("In progress", "Kirby") or a count (`1`). It stays in view when the label gives way, and follows the label in the accessible name. A count (a number, or a string of digits) is set in Space Mono, as machine-written text; any other value keeps the label's font. |
| `focusableWhenDisabled` | `boolean` | `false` | Whether the button should be focusable when disabled. |
| `nativeButton` | `boolean` | `true` | Whether the component renders a native `<button>` element when replacing it via the `render` prop. Set to `false` if the rendered element is not a button (for example, `<div>`). |

Also takes the attributes of its element.

## Changes

- 0.83.0 (Added): **FitToolbar** (from `@botiverse/raft-ui/wip`): a Base UI Toolbar whose items give way in named steps as the region it sits in narrows: `label` (to an icon), `more` (into More), `collapse` (a group to one button) and `wrap` (a group to a line of its own, with `wrap`; groups with the same `wrapLine` share one, and a wrapped group gets back what its items gave before it wrapped). Items declare their steps and order with `useFitItem`, or are a `FitToolbarButton`; a `FitToolbarGroup overflow="scroll"` scrolls sideways instead, its `data-active` items kept in view; groups are `FitToolbarGroup` (`firstLine`, `wrapOrder`, `wrapLine`, `collapsed`, `collapseOrder`); More takes `useFitMore` and lists `FitToolbarMoreItems`. It measures its own region's width, never the window's, again when its content changes on its own, and writes each step as `data-fit-<step>`. A SegmentedControl inside it follows its `label` step; its own `iconsOnlyBelow` is off there.
