# SelectionToolbar

> The toolbar that floats at the bottom of a list or conversation while items are selected, with the count and the actions on them, such as forwarding messages or changing tasks in bulk.

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

`SelectionToolbar` floats at the bottom of a list or conversation while several items are selected: the count, then the actions on those items, then × to stop selecting. Messages (Forward) and tasks (bulk actions) share it, in the BatchBar style (Noel; stdrc, 2026-11-10, #wg-task-v2), so selecting reads the same everywhere. Its rules are in [Toolbar for selected items](https://raft-design.botiverse.build/components.md#toolbar-for-selected-items). It is a [FitToolbar](https://raft-design.botiverse.build/components/fit-toolbar.md) that never wraps.

```tsx
import { useCallback, useMemo, useState } from "react";
import type { MouseEvent } from "react";
import { Bell, CircleDot, Eye, Trash2, UserRound } from "lucide-react";
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuSubmenu,
  DropdownMenuSubmenuTrigger,
  DropdownMenuTrigger,
  Text,
} from "@botiverse/raft-ui";
import {
  SelectionToolbar,
  SelectionToolbarAction,
  SelectionToolbarActions,
  SelectionToolbarClose,
  SelectionToolbarCount,
  SelectionToolbarCountValue,
  SelectionToolbarDock,
  SelectionToolbarHint,
  SelectionToolbarMore,
  SelectionToolbarMoreItems,
} from "@botiverse/raft-ui/wip";

const chosen = (event: MouseEvent<HTMLElement>) => event.currentTarget.dataset.value ?? "";

export function SelectionToolbarContent() {
  const [last, setLast] = useState("");
  const [portalContainer, setPortalContainer] = useState<HTMLDivElement | null>(null);
  const portalProps = useMemo(
    () => (portalContainer ? { container: portalContainer } : undefined),
    [portalContainer],
  );
  const setStatus = useCallback(
    (event: MouseEvent<HTMLElement>) => setLast(`2 tasks moved to ${chosen(event)}, 1 skipped`),
    [],
  );
  const assign = useCallback(
    (event: MouseEvent<HTMLElement>) => setLast(`2 tasks assigned to ${chosen(event)}, 1 skipped`),
    [],
  );
  const nudge = useCallback(() => setLast("Nudged the assignees of 3 tasks"), []);
  const follow = useCallback(() => setLast("Following 3 tasks"), []);
  const remove = useCallback(() => setLast("3 tasks deleted"), []);
  const clear = useCallback(() => setLast("Selection cleared"), []);

  const statusItems = (
    <>
      <DropdownMenuItem data-value="To do" onClick={setStatus}>
        To do
      </DropdownMenuItem>
      <DropdownMenuItem data-value="In progress" onClick={setStatus}>
        In progress
      </DropdownMenuItem>
      <DropdownMenuItem data-value="Done" onClick={setStatus}>
        Done
      </DropdownMenuItem>
    </>
  );
  const assigneeItems = (
    <>
      <DropdownMenuItem data-value="you" onClick={assign}>
        Me
      </DropdownMenuItem>
      <DropdownMenuItem data-value="Noel" onClick={assign}>
        Noel
      </DropdownMenuItem>
      <DropdownMenuItem data-value="Kirby" onClick={assign}>
        Kirby
      </DropdownMenuItem>
    </>
  );

  return (
    <div ref={setPortalContainer} className="relative h-40 w-full">
      <Text.Sans color="muted" size="small" role="status" className="p-4">
        {last}
      </Text.Sans>
      <SelectionToolbarDock>
        <SelectionToolbar aria-label="Selected task actions">
          <SelectionToolbarCount>
            <SelectionToolbarCountValue>3</SelectionToolbarCountValue> selected
          </SelectionToolbarCount>
          <SelectionToolbarHint>1 will be skipped</SelectionToolbarHint>
          <SelectionToolbarActions>
            <DropdownMenu>
              <DropdownMenuTrigger
                render={
                  <SelectionToolbarAction
                    menu
                    icon={<CircleDot />}
                    moreItem={
                      <DropdownMenuSubmenu>
                        <DropdownMenuSubmenuTrigger>
                          <CircleDot size={14} />
                          Status
                        </DropdownMenuSubmenuTrigger>
                        <DropdownMenuContent portalProps={portalProps}>
                          {statusItems}
                        </DropdownMenuContent>
                      </DropdownMenuSubmenu>
                    }
                  >
                    Status
                  </SelectionToolbarAction>
                }
              />
              <DropdownMenuContent
                side="top"
                align="start"
                sideOffset={8}
                portalProps={portalProps}
              >
                {statusItems}
              </DropdownMenuContent>
            </DropdownMenu>
            <DropdownMenu>
              <DropdownMenuTrigger
                render={
                  <SelectionToolbarAction
                    menu
                    icon={<UserRound />}
                    moreItem={
                      <DropdownMenuSubmenu>
                        <DropdownMenuSubmenuTrigger>
                          <UserRound size={14} />
                          Assignee
                        </DropdownMenuSubmenuTrigger>
                        <DropdownMenuContent portalProps={portalProps}>
                          {assigneeItems}
                        </DropdownMenuContent>
                      </DropdownMenuSubmenu>
                    }
                  >
                    Assignee
                  </SelectionToolbarAction>
                }
              />
              <DropdownMenuContent
                side="top"
                align="start"
                sideOffset={8}
                portalProps={portalProps}
              >
                {assigneeItems}
              </DropdownMenuContent>
            </DropdownMenu>
            <SelectionToolbarAction icon={<Bell />} onClick={nudge}>
              Nudge
            </SelectionToolbarAction>
            <SelectionToolbarAction secondary icon={<Eye />} onClick={follow}>
              Follow
            </SelectionToolbarAction>
            <SelectionToolbarAction secondary icon={<Trash2 />} onClick={remove}>
              Delete
            </SelectionToolbarAction>
            <DropdownMenu>
              <DropdownMenuTrigger
                render={<SelectionToolbarMore aria-label="More selected task actions" />}
              />
              <DropdownMenuContent side="top" align="end" sideOffset={8} portalProps={portalProps}>
                <SelectionToolbarMoreItems />
              </DropdownMenuContent>
            </DropdownMenu>
          </SelectionToolbarActions>
          <SelectionToolbarClose
            aria-label="Clear selection"
            title="Clear selection"
            onClick={clear}
          />
        </SelectionToolbar>
      </SelectionToolbarDock>
    </div>
  );
}

export function SelectionToolbarComposition() {
  return (
    <div className="flex size-full items-end justify-center">
      <SelectionToolbarContent />
    </div>
  );
}
```

## When to use

| Situation                                            | Use                                                                                                                                      |
| ---------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| Actions on several selected messages or tasks        | SelectionToolbar                                                                                                                         |
| Selected rows in a list with a header row (Activity) | [ActivityInbox](https://raft-design.botiverse.build/components/activity-inbox.md)'s selection toolbar, inline in the header, which can wrap because it covers nothing |
| Actions on text the person selected                  | [TextSelectionToolbar](https://raft-design.botiverse.build/components/text-selection-toolbar.md)                                                              |
| Actions on one message under the pointer             | The message's hover toolbar ([MessageItem](https://raft-design.botiverse.build/components/message-item.md))                                                             |
| Actions on one row                                   | The row's own menu ([DropdownMenu](https://raft-design.botiverse.build/components/dropdown-menu.md))                                                                   |

## Anatomy

In this order:

- `SelectionToolbarDock`: where it floats, centered at the bottom of its positioned parent. The toolbar measures what fits in its width.
- `SelectionToolbar`: the floating frame, a Base UI Toolbar (`role="toolbar"`). Required.
- `SelectionToolbarItem`: any other control of the toolbar, such as a thread's select-all checkbox before the count: wrap it (`<SelectionToolbarItem><Checkbox /></SelectionToolbarItem>`) so the arrow keys reach it; it keeps its own role and keys.
- `SelectionToolbarCount`: "3 selected", with the number in `SelectionToolbarCountValue`. Required.
- `SelectionToolbarHint`: an optional short line after the count, such as "1 will be skipped".
- `SelectionToolbarActions`: the surface's actions.
- `SelectionToolbarAction`: one action, an icon and a label. `secondary` makes it the first to move into More; `menu` adds the chevron of an action that opens a menu; `moreItem` is what it is in More's menu (by default a menu item with its icon and label that calls its `onClick`; give a `menu` action a `DropdownMenuSubmenu`).
- `SelectionToolbarMore`: More, after the actions, shown only while some are in it. Put `SelectionToolbarMoreItems` in its menu: it lists them, in order.
- `SelectionToolbarClose`: ×, last. Required.

The stable `MessageMultiSelectToolbar` and `MessageMultiSelectToolbarActions` keep their earlier bar until SelectionToolbar leaves wip, so a change here is not a change to them.

```tsx
import { useCallback, useMemo, useRef, useState } from "react";
import type { KeyboardEvent } from "react";
import { Check, FileText, Forward, Image as ImageIcon, Link } from "lucide-react";
import {
  Avatar,
  Checkbox,
  AvatarFallback,
  AvatarImage,
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuTrigger,
  MessageItem,
  MessageItemAvatarSlot,
  MessageItemBody,
  MessageItemContent,
  MessageItemHeader,
  MessageItemSender,
  MessageItemTime,
  MessageMultiSelect,
  MessageMultiSelectCheckbox,
  TextLink,
} from "@botiverse/raft-ui";
import {
  SelectionToolbar,
  SelectionToolbarAction,
  SelectionToolbarActions,
  SelectionToolbarClose,
  SelectionToolbarCount,
  SelectionToolbarCountValue,
  SelectionToolbarDock,
  SelectionToolbarItem,
  SelectionToolbarMore,
  SelectionToolbarMoreItems,
} from "@botiverse/raft-ui/wip";

import { MESSAGE_FIXTURE_AVATAR_SRC } from "../message-item.fixtures.ts";

const MESSAGE_IDS = ["decision", "reply"] as const;

const messages = (count: number) => `${count} ${count === 1 ? "message" : "messages"}`;

export function MessageSelectionShowcaseComposition() {
  const [active, setActive] = useState(true);
  const [selected, setSelected] = useState<ReadonlySet<string>>(() => new Set(["decision"]));
  const [copied, setCopied] = useState<"link" | "markdown" | null>(null);
  const [announcement, setAnnouncement] = useState("");
  const [portalContainer, setPortalContainer] = useState<HTMLDivElement | null>(null);
  const reenterRef = useRef<HTMLButtonElement>(null);
  const count = selected.size;
  const portalProps = useMemo(
    () => (portalContainer ? { container: portalContainer } : undefined),
    [portalContainer],
  );

  const setChecked = useCallback((id: string, checked: boolean) => {
    setSelected((current) => {
      const next = new Set(current);
      if (checked) next.add(id);
      else next.delete(id);
      return next;
    });
  }, []);
  const handleDecisionChange = useCallback(
    (checked: boolean) => setChecked("decision", checked),
    [setChecked],
  );
  // In a thread: from none it selects all; from some or all it clears, so a
  // partial selection is never swapped for everything by surprise.
  const handleSelectAll = useCallback(() => {
    setSelected((current) => (current.size === 0 ? new Set(MESSAGE_IDS) : new Set()));
  }, []);
  const handleReplyChange = useCallback(
    (checked: boolean) => setChecked("reply", checked),
    [setChecked],
  );
  const showCopied = useCallback((what: "link" | "markdown") => {
    setCopied(what);
    window.setTimeout(() => setCopied(null), 1500);
  }, []);
  const handleCopyLink = useCallback(() => showCopied("link"), [showCopied]);
  const handleCopyMarkdown = useCallback(() => showCopied("markdown"), [showCopied]);
  const handleForward = useCallback(
    () => setAnnouncement(`Forwarding ${messages(count)}`),
    [count],
  );
  const handleGenerateImage = useCallback(
    () => setAnnouncement(`Generating an image of ${messages(count)}`),
    [count],
  );
  const handleCancel = useCallback(() => {
    setActive(false);
    setSelected(new Set());
    setAnnouncement("");
    // The toolbar is gone: focus moves to what took its place, not the page.
    requestAnimationFrame(() => reenterRef.current?.focus());
  }, []);
  // Escape ends the selection, as × does; inside the More menu it only closes the menu.
  const handleKeyDown = useCallback(
    (event: KeyboardEvent<HTMLDivElement>) => {
      if (event.key !== "Escape" || event.defaultPrevented) return;
      if (event.target instanceof Element && event.target.closest('[role="menu"]')) return;
      event.preventDefault();
      handleCancel();
    },
    [handleCancel],
  );
  const handleReenter = useCallback(() => {
    setActive(true);
    setSelected(new Set(["decision"]));
  }, []);

  if (!active) {
    return (
      <TextLink ref={reenterRef} className="text-footnote" onClick={handleReenter}>
        Select messages
      </TextLink>
    );
  }

  return (
    <div
      ref={setPortalContainer}
      className="relative w-full max-w-3xl pb-20"
      onKeyDown={handleKeyDown}
    >
      <MessageMultiSelect>
        <MessageItem>
          <MessageMultiSelectCheckbox
            aria-label="Select message from Maya Chen"
            checked={selected.has("decision")}
            value="decision"
            onCheckedChange={handleDecisionChange}
          />
          <MessageItemAvatarSlot>
            <Avatar size="md" type="human">
              <AvatarFallback>MC</AvatarFallback>
            </Avatar>
          </MessageItemAvatarSlot>
          <MessageItemContent>
            <MessageItemHeader>
              <MessageItemSender>Maya Chen</MessageItemSender>
              <MessageItemTime>10:04 AM</MessageItemTime>
            </MessageItemHeader>
            <MessageItemBody>
              Decision from yesterday's review: we keep the skip link on every step.
            </MessageItemBody>
          </MessageItemContent>
        </MessageItem>
        <MessageItem>
          <MessageMultiSelectCheckbox
            aria-label="Select message from Remy"
            checked={selected.has("reply")}
            value="reply"
            onCheckedChange={handleReplyChange}
          />
          <MessageItemAvatarSlot>
            <Avatar size="md" type="agent">
              <AvatarImage src={MESSAGE_FIXTURE_AVATAR_SRC} alt="" />
              <AvatarFallback>RE</AvatarFallback>
            </Avatar>
          </MessageItemAvatarSlot>
          <MessageItemContent>
            <MessageItemHeader>
              <MessageItemSender>Remy</MessageItemSender>
              <MessageItemTime>10:09 AM</MessageItemTime>
            </MessageItemHeader>
            <MessageItemBody>Agreed. The copy for step 2 should say "server".</MessageItemBody>
          </MessageItemContent>
        </MessageItem>
      </MessageMultiSelect>

      <SelectionToolbarDock>
        <SelectionToolbar aria-label="Selected message actions">
          <SelectionToolbarItem>
            <Checkbox
              aria-label="Select all messages in this thread"
              checked={count === MESSAGE_IDS.length}
              indeterminate={count > 0 && count < MESSAGE_IDS.length}
              onCheckedChange={handleSelectAll}
            />
          </SelectionToolbarItem>
          <SelectionToolbarCount>
            {count === MESSAGE_IDS.length ? "All " : ""}
            <SelectionToolbarCountValue>{count}</SelectionToolbarCountValue> selected
          </SelectionToolbarCount>
          <SelectionToolbarActions>
            <SelectionToolbarAction
              icon={<Forward />}
              disabled={count === 0}
              onClick={handleForward}
            >
              Forward
            </SelectionToolbarAction>
            <SelectionToolbarAction
              secondary
              icon={<ImageIcon />}
              disabled={count === 0}
              onClick={handleGenerateImage}
            >
              Generate image
            </SelectionToolbarAction>
            <SelectionToolbarAction
              secondary
              icon={copied === "link" ? <Check /> : <Link />}
              disabled={count === 0}
              onClick={handleCopyLink}
            >
              {copied === "link" ? "Copied" : "Copy link"}
            </SelectionToolbarAction>
            <SelectionToolbarAction
              secondary
              icon={copied === "markdown" ? <Check /> : <FileText />}
              disabled={count === 0}
              onClick={handleCopyMarkdown}
            >
              {copied === "markdown" ? "Copied" : "Copy Markdown"}
            </SelectionToolbarAction>
            <DropdownMenu>
              <DropdownMenuTrigger
                render={<SelectionToolbarMore aria-label="More selected message actions" />}
              />
              <DropdownMenuContent side="top" align="end" sideOffset={8} portalProps={portalProps}>
                <SelectionToolbarMoreItems />
              </DropdownMenuContent>
            </DropdownMenu>
          </SelectionToolbarActions>
          <SelectionToolbarClose aria-label="Cancel" title="Cancel" onClick={handleCancel} />
        </SelectionToolbar>
      </SelectionToolbarDock>
      <span className="sr-only" role="status">
        {announcement}
      </span>
    </div>
  );
}
```

## Look

- A floating panel's frame: `radius-container`, `layer-popover`, a `line-seam` ring and `shadow-float`, as every floating panel ([Elevation](https://raft-design.botiverse.build/foundations/elevation.md)). It is as tall as a head with a head's air at its ends (`header-height` and `header-air`): 44px and 5px with a mouse on a desktop, 56px and 6px on a phone and a touch screen. It replaces the composer at the bottom of a column, so it takes a head's measure (Noel). It is one row and never wraps.
- The count is `text-callout` bold, 8px on its left (and its right with a mouse), with the number in Space Mono, as counts are. The hint is `text-caption` in `foreground-hint`.
- With a mouse every button is an `md` ghost [Button](https://raft-design.botiverse.build/components/button.md), 34px, 4px apart, with a 14px icon and its label, as the BatchBar spec draws it (Noel); a menu action's 12px chevron follows its label. An action whose label has given way is a 34px square; More and × are too.
- On a phone and a touch screen the buttons are a head's bar buttons, 44px (`bar-button-size`), side by side, showing their icons alone with no chevrons, as a head's buttons are by touch: at 360px a phone then fits the count, Status, Assignee, Nudge, More and × (Noel, 2026-11-10).
- There is no primary button, and no pink: while several items are selected, no action outranks the others (stdrc, 2026-11-10).

## Behavior

- `SelectionToolbarDock` floats it 12px above the bottom of the list or conversation, plus the safe area, as the conversation's other floating controls, so it sits above a phone's bottom navigation, and 20px from the sides, the composer's edge.
- When its row doesn't fit the width it floats in, measured in that width and never read from the window, things give way one step at a time ([FitToolbar](https://raft-design.botiverse.build/components/fit-toolbar.md)): the hint (then read by screen readers only), the `secondary` actions into More from the end, the labels from the end (so the first action's goes last; each then shows its label as a tooltip), then the other actions into More from the end. The count and × never give way. It never wraps, because a second line would cover the items under it ([When a row runs out of room](https://raft-design.botiverse.build/patterns/responsive.md#when-a-row-runs-out-of-room)).
- Open the menus of a menu action and of More upward: `side="top"` on `DropdownMenuContent`.
- With a mouse each button's hit area reaches only half the 4px gap toward its neighbour, so a click between two lands on the nearer; by touch each 44px button is its own tap.

## Content

- The count says what is selected, as a number and a word: "3 selected". In a thread with a select-all checkbox, "All 169 selected" when all are ([Toolbar for selected items](https://raft-design.botiverse.build/components.md#toolbar-for-selected-items)).
- Keep the hint to a few words about what the actions will do to the selection, such as "1 will be skipped". It is the first to give way, so don't put anything there the person needs.
- Write labels in sentence case: Forward, Generate image, Copy link, Copy Markdown. Order the actions by why people select the items, the most wanted first: it keeps its label longest. For messages Forward first, Generate image next (it shares them out too), the two copies last, the last three `secondary`. For tasks Status, Assignee, Nudge, then Follow and Delete marked `secondary`: they are the first into More when the row is narrow (Noel, 2026-11-10).

## Accessibility

- Give `SelectionToolbar` an `aria-label` that stays the same while the count changes, such as "Selected message actions". It is one Tab stop: Left and Right move between its controls, skipping those in More (Base UI Toolbar).
- `SelectionToolbarCount` is a polite live region: the new count is announced when it changes.
- An action showing its icon alone keeps its label as its accessible name and shows it as a tooltip.
- `SelectionToolbarMore` and `SelectionToolbarClose` take an `aria-label`, such as "More actions" and "Cancel".
- End the selection on Escape, as × does, unless a menu is open; then move the focus back into the conversation: to the composer, or where there is none, to the item the selection started on ([Moving focus](https://raft-design.botiverse.build/foundations/accessibility.md#moving-focus)). The toolbar does not do this for you: the selection is yours.

## API

### SelectionToolbar

The floating toolbar for selected items (messages, tasks): the count, an optional hint, the actions on the selection, More while some are out of the row, then ×. One row at any width: what doesn't fit gives way, measured in the width it floats in, and never wraps. A `role="toolbar"`, one Tab stop with the arrow keys inside: give it an `aria-label`.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `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. |
| `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.

### SelectionToolbarAction

One action on the selection: a 14px icon and a label, an `md` ghost button with a mouse on a desktop, a 44px bar button showing its icon alone on a phone and a touch screen.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `children` (required) | `ReactNode` |  | The label. While only the icon shows, the label is its accessible name and its tooltip. |
| `icon` (required) | `ReactNode` |  | The action's 14px icon. |
| `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. |
| `menu` | `boolean` | `false` | The action opens a menu: a 12px chevron follows the label. Open the menu upward. |
| `moreItem` | `ReactNode` |  | What the action is in More's menu while it is there. By default a `DropdownMenuItem` with its icon and label that calls its `onClick`. Required: its menu in More's (a `DropdownMenuSubmenu` with the same choices). Its `onClick` only opens its own menu, so it has no default, and without one it would vanish into More. |
| `secondary` | `boolean` | `false` | Moves into More before the labels give way, the last first. |
| `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.

### SelectionToolbarActions

The actions on the selection, in one row.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `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. |
| `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.

### SelectionToolbarClose

×, last: ends the selection, as Escape does.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `aria-label` (required) | `string` |  | Its accessible name, such as "Cancel". |
| `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. |
| `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.

### SelectionToolbarCount

What the actions act on ("3 selected"), first. Announced when it changes. Put the number in `SelectionToolbarCountValue`.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `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.

### SelectionToolbarCountValue

The number in the count, in Space Mono.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `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.

### SelectionToolbarDock

Where the toolbar floats: centered, 12px above the bottom of the list or conversation it sits in (plus the safe area), 20px from its sides. Its parent must be positioned.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `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.

### SelectionToolbarHint

A short line after the count, such as "1 will be skipped". The first to give way: it is then read by screen readers only.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `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.

### SelectionToolbarItem

Any other control of the toolbar, such as a thread's select-all checkbox before the count, so the arrow keys reach it. It takes the toolbar's focus handling only: the control keeps its role and its own keys.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `children` (required) | `ReactElement<Record<string, unknown>, string \| JSXElementConstructor<any>>` |  | The control, such as `<Checkbox />`. It keeps its own role and keys. |

### SelectionToolbarMore

More, after the actions, shown only while some are in it. Render it as a `DropdownMenuTrigger` whose menu opens upward (`side="top"`), with `SelectionToolbarMoreItems` in the menu.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `aria-label` (required) | `string` |  | Its accessible name, such as "More actions". |
| `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. |
| `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.

## Changes

- 0.83.0 (Added): **SelectionToolbar** (from `@botiverse/raft-ui/wip`): the floating toolbar for selected items (messages, tasks), in the BatchBar style. `SelectionToolbarDock` floats it 12px above the bottom (plus the safe area) and 20px from the sides; `SelectionToolbarCount` (with the number in `SelectionToolbarCountValue`, Space Mono), `SelectionToolbarHint`, `SelectionToolbarActions` with `SelectionToolbarAction` (`secondary`, `menu`, `moreItem`), `SelectionToolbarMore` with `SelectionToolbarMoreItems` in its menu, `SelectionToolbarItem` for another control such as a select-all checkbox, and `SelectionToolbarClose`. A floating panel's frame (`line-seam` ring, `shadow-float`) as tall as a head: 44px with `md` buttons 4px apart with a mouse, 56px with 44px bar buttons side by side showing their icons alone by touch. It is a Base UI Toolbar (one Tab stop, arrow keys inside) and never wraps: when its row doesn't fit the width it floats in, measured, the hint, the secondary actions, the labels (the first action's last) and then the other actions give way, the last first; the count and × stay.
