Menu
Menu экономит место для набора связанных команд или переходов, сохраняет клавиатурную навигацию и закрывается после выбора.
Статус: experimental · в системе с 2026-07 · интерактивность: always (всегда)
Когда использовать
- Несколько вторичных действий относятся к одному объекту или области.
- Связанные переходы не должны постоянно занимать место на экране.
Не использовать:
- Одно главное действие должно быть видно сразу — используй Button.
- Нужна постоянная навигация между разделами — используй Link или Tabs.
- Пользователь выбирает значение формы — используй Select.
Альтернативы:
Действия
Один список вторичных команд для объекта.
Действие ещё не выбрано.
<!--
🟢
Базовый пример Menu собирает вторичные действия одного проекта.
Выбранный пункт сообщает результат вне раскрывающейся панели.
-->
<script>
import { t } from "$lib/spec/demo-lang.svelte.js";
import Button from "$lib/ui/Button.svelte";
import Menu from "$lib/ui/Menu.svelte";
const items = [
{ id: "rename", label: t("Переименовать"), shortcut: "R" },
{ id: "duplicate", label: t("Дублировать"), shortcut: "D" },
{ id: "archive", label: t("Архивировать") },
];
let result = $state(t("Действие ещё не выбрано."));
</script>
<Menu {items} onselect={(item) => (result = `${t("Выбрано:")} ${item.label}`)}>
{#snippet trigger({ props })}
<Button {...props}>{t("Действия проекта")}</Button>
{/snippet}
</Menu>
<p aria-live="polite">{result}</p>
Ссылки и группы
Переходы сгруппированы по назначению и разделены от отдельной команды.
<!--
🟢
Пример Menu показывает навигационные ссылки внутри именованных групп.
Отдельная команда отделена от переходов смысловым разделителем.
-->
<script>
import { t } from "$lib/spec/demo-lang.svelte.js";
import Button from "$lib/ui/Button.svelte";
import Menu from "$lib/ui/Menu.svelte";
const items = [
{
id: "workspace",
label: t("Рабочая область"),
items: [
{ id: "overview", label: t("Обзор"), href: "/menu" },
{ id: "settings", label: t("Настройки"), href: "/menu" },
],
},
{ id: "separator", type: "separator" },
{ id: "help", label: t("Открыть справку"), href: "/menu" },
];
</script>
<Menu {items} align="start">
{#snippet trigger({ props })}
<Button variant="ghost" {...props}>{t("Перейти к разделу")}</Button>
{/snippet}
</Menu>
Разрушительное действие
Danger обозначает смысл, а подтверждение остаётся отдельным паттерном.
Проект не изменён.
<!--
🟢
Пример Menu отделяет разрушительное действие и маркирует его danger-тоном.
Само подтверждение удаления должно появиться после выбора в отдельном Dialog.
-->
<script>
import { t } from "$lib/spec/demo-lang.svelte.js";
import Button from "$lib/ui/Button.svelte";
import Menu from "$lib/ui/Menu.svelte";
const items = [
{ id: "export", label: t("Экспортировать") },
{ id: "separator", type: "separator" },
{ id: "delete", label: t("Удалить проект"), tone: "danger" },
];
let result = $state(t("Проект не изменён."));
</script>
<Menu {items} onselect={(item) => (result = `${t("Запрошено:")} ${item.label}`)}>
{#snippet trigger({ props })}
<Button {...props}>{t("Другие действия")}</Button>
{/snippet}
</Menu>
<p aria-live="polite">{result}</p>
Компактное
size="s": панель и пункты с малыми отступами для плотных тулбаров.
<!--
🟢
Компактное меню (эталон: Vue-легаси menu-list._s / menu-item._s): панель
и пункты с малыми отступами и кеглем 12 — для плотных тулбаров и ячеек
таблицы.
-->
<script>
import { t } from "$lib/spec/demo-lang.svelte.js";
import Button from "$lib/ui/Button.svelte";
import Menu from "$lib/ui/Menu.svelte";
const items = [
{ id: "edit", label: t("Редактировать") },
{ id: "copy", label: t("Дублировать") },
{ id: "archive", label: t("В архив") },
];
</script>
<Menu {items} size="s">
{#snippet trigger({ props })}
<Button {...props} variant="ghost" size="s" short icon="DotsThree" aria-label={t("Действия")} />
{/snippet}
</Menu>