Toolbar
Toolbar собирает локальные action- и select-взаимодействия из закрытого items-union. Bits UI держит одну точку Tab и перемещает фокус стрелками согласно orientation.
Статус: experimental · в системе с 2026-07 · интерактивность: always (всегда)
Когда использовать
- Несколько команд и локальных режимов управляют одной рабочей областью.
- Действия, ссылки и выбор должны образовать одну последовательность roving focus.
- Вертикальная или горизонтальная панель имеет короткие однозначные подписи.
Не использовать:
- Нужно одно самостоятельное действие — используй Button.
- Нужно раскрывающееся меню вторичных команд — используй Menu.
- Набор вариантов является полем формы — используй Radio, Checkbox или Toggle Group вне панели.
Альтернативы:
button— одноразовое действие без общей панелиmenu— вторичные команды должны раскрываться по запросуtoggle-group— вся коллекция состоит только из связанных toggle-режимов
Действия и ссылка
Одноразовые команды и навигация остаются разными нативными элементами.
Действие ещё не выбрано
<!--
🟢
Пример действий Toolbar разделяет одноразовые команды и навигацию. Кнопки
сообщают выбранный item, а ссылка сохраняет нативный переход браузера.
-->
<script>
import { t } from "$lib/spec/demo-lang.svelte.js";
import Toolbar from "$lib/ui/Toolbar.svelte";
import ArrowSquareOut from "phosphor-svelte/lib/ArrowSquareOut";
import Copy from "phosphor-svelte/lib/Copy";
import FloppyDisk from "phosphor-svelte/lib/FloppyDisk";
const items = [
{
id: "save",
type: "action",
label: t("Сохранить"),
icon: FloppyDisk,
},
{
id: "copy",
type: "action",
label: t("Копировать"),
icon: Copy,
},
{
id: "overview",
type: "link",
label: t("Обзор"),
href: "/overview",
icon: ArrowSquareOut,
},
];
let result = $state(t("Действие ещё не выбрано"));
</script>
<div style="display: grid; gap: var(--sp-2); justify-items: start;">
<Toolbar
label={t("Действия редактора")}
{items}
onaction={(item) => (result = `${t("Выбрано:")} ${item.label}`)}
/>
<p aria-live="polite">{result}</p>
</div>
Режимы
Standalone toggle и multiple-группа сообщают разные формы nextValue.
Предпросмотр: включён; начертание: bold
<!--
🟢
Пример режимов Toolbar сочетает один boolean-toggle и multiple-группу.
Владелец данных применяет nextValue к соответствующей ветке items-union.
-->
<script>
import { t } from "$lib/spec/demo-lang.svelte.js";
import Toolbar from "$lib/ui/Toolbar.svelte";
import Eye from "phosphor-svelte/lib/Eye";
import TextB from "phosphor-svelte/lib/TextB";
import TextItalic from "phosphor-svelte/lib/TextItalic";
let items = $state([
{
id: "preview",
type: "toggle",
label: t("Предпросмотр"),
pressed: true,
icon: Eye,
},
{
id: "format",
type: "group",
label: t("Начертание"),
selectionMode: "multiple",
value: ["bold"],
items: [
{ value: "bold", label: t("Жирный"), icon: TextB },
{ value: "italic", label: t("Курсив"), icon: TextItalic },
],
},
]);
function applyChange(item, nextValue) {
items = items.map((candidate) => {
if (candidate.id !== item.id) return candidate;
return candidate.type === "toggle"
? { ...candidate, pressed: nextValue }
: { ...candidate, value: nextValue };
});
}
let summary = $derived.by(() => {
const preview = items.find((item) => item.id === "preview");
const format = items.find((item) => item.id === "format");
const modes = format.value.length ? format.value.join(", ") : t("нет");
return `${t("Предпросмотр:")} ${preview.pressed ? t("включён") : t("выключен")}${t("; начертание:")} ${modes}`;
});
</script>
<div style="display: grid; gap: var(--sp-2); justify-items: start;">
<Toolbar label={t("Режимы редактора")} {items} onchange={applyChange} />
<p aria-live="polite">{summary}</p>
</div>
Ориентации
Horizontal и vertical меняют раскладку и направление стрелок.
<!--
🟢
Пример ориентаций сравнивает строку действий и вертикальную боковую панель.
Визуальная ось совпадает с направлением стрелок, а loop можно отключить.
-->
<script>
import { t } from "$lib/spec/demo-lang.svelte.js";
import Toolbar from "$lib/ui/Toolbar.svelte";
import ListBullets from "phosphor-svelte/lib/ListBullets";
import ListNumbers from "phosphor-svelte/lib/ListNumbers";
import PencilSimple from "phosphor-svelte/lib/PencilSimple";
const horizontalItems = [
{ id: "edit", type: "action", label: t("Редактировать"), icon: PencilSimple },
{
id: "lists",
type: "group",
label: t("Тип списка"),
selectionMode: "single",
value: "bullets",
items: [
{ value: "bullets", label: t("Маркеры"), icon: ListBullets },
{ value: "numbers", label: t("Нумерация"), icon: ListNumbers },
],
},
];
const verticalItems = [
{ id: "edit", type: "action", label: t("Редактировать"), icon: PencilSimple },
{
id: "bullets",
type: "toggle",
label: t("Показывать маркеры"),
pressed: true,
icon: ListBullets,
},
{
id: "numbers",
type: "toggle",
label: t("Показывать нумерацию"),
pressed: false,
icon: ListNumbers,
},
];
</script>
<div style="display: grid; gap: var(--sp-3); justify-items: start;">
<Toolbar
label={t("Горизонтальная панель")}
items={horizontalItems}
orientation="horizontal"
/>
<Toolbar
label={t("Вертикальная панель")}
items={verticalItems}
orientation="vertical"
loop={false}
/>
</div>