<script setup lang="ts">
import { ref } from 'vue'
import {
Kbd,
Menubar,
MenubarCheckboxItem,
MenubarItem,
MenubarMenu,
MenubarRadioGroup,
MenubarRadioItem,
MenubarSeparator,
MenubarSub,
Stack,
Text,
} from '@hina-ui/vue'
const last = ref('')
const wrap = ref(true)
const numbers = ref(false)
const theme = ref('system')
</script>
<template>
<Stack gap="sm" align="start">
<Menubar label="Editor menu">
<MenubarMenu label="File">
<MenubarItem @select="last = 'New'">
New
<template #trailing><Kbd>Ctrl N</Kbd></template>
</MenubarItem>
<MenubarItem @select="last = 'Open'">
Open
<template #trailing><Kbd>Ctrl O</Kbd></template>
</MenubarItem>
<MenubarSub label="Open recent">
<MenubarItem @select="last = 'chapter-1.md'">chapter-1.md</MenubarItem>
<MenubarItem @select="last = 'outline.md'">outline.md</MenubarItem>
</MenubarSub>
<MenubarSeparator />
<MenubarItem @select="last = 'Save'">
Save
<template #trailing><Kbd>Ctrl S</Kbd></template>
</MenubarItem>
</MenubarMenu>
<MenubarMenu label="Edit">
<MenubarItem @select="last = 'Undo'">
Undo
<template #trailing><Kbd>Ctrl Z</Kbd></template>
</MenubarItem>
<MenubarItem @select="last = 'Redo'">
Redo
<template #trailing><Kbd>Ctrl Y</Kbd></template>
</MenubarItem>
<MenubarSeparator />
<MenubarItem @select="last = 'Find'">
Find
<template #trailing><Kbd>Ctrl F</Kbd></template>
</MenubarItem>
</MenubarMenu>
<MenubarMenu label="View">
<MenubarCheckboxItem v-model:checked="wrap">Word wrap</MenubarCheckboxItem>
<MenubarCheckboxItem v-model:checked="numbers">Line numbers</MenubarCheckboxItem>
<MenubarSeparator />
<MenubarRadioGroup v-model="theme">
<MenubarRadioItem value="light">Light</MenubarRadioItem>
<MenubarRadioItem value="dark">Dark</MenubarRadioItem>
<MenubarRadioItem value="system">System</MenubarRadioItem>
</MenubarRadioGroup>
</MenubarMenu>
</Menubar>
<Text v-if="last" tone="muted" size="sm">Picked: {{ last }}</Text>
</Stack>
</template>
Usage
import { Menubar, MenubarMenu, MenubarItem } from '@hina-ui/vue'
Menubar is a horizontal bar holding several MenubarMenus; each menu’s label is its trigger text on the bar and its default slot holds the items. Clicking a trigger opens the menu, and while one is open moving the pointer onto another trigger switches to it. Items, checkbox items, radio items, groups, labels, separators and submenus are the same set as in DropdownMenu, only named with the Menubar prefix.
<script setup lang="ts">
import { Menubar, MenubarItem, MenubarMenu } from '@hina-ui/vue'
</script>
<template>
<Menubar label="Example menu">
<MenubarMenu label="File">
<MenubarItem>New</MenubarItem>
<MenubarItem>Open</MenubarItem>
<MenubarItem>Save</MenubarItem>
</MenubarMenu>
<MenubarMenu label="Edit">
<MenubarItem>Undo</MenubarItem>
<MenubarItem>Redo</MenubarItem>
</MenubarMenu>
<MenubarMenu label="Help">
<MenubarItem>Documentation</MenubarItem>
<MenubarItem>About</MenubarItem>
</MenubarMenu>
</Menubar>
</template>
Examples
Items
The icon slot of an item holds a leading icon and trailing holds trailing content such as a shortcut; give irreversible actions such as delete tone="danger". MenubarLabel is a title that cannot be selected, and MenubarSeparator draws a line.
<script setup lang="ts">
import { Copy, FilePlus, Trash2 } from '@lucide/vue'
import {
Kbd,
Menubar,
MenubarItem,
MenubarLabel,
MenubarMenu,
MenubarSeparator,
} from '@hina-ui/vue'
</script>
<template>
<Menubar label="Item example">
<MenubarMenu label="File">
<MenubarLabel>This document</MenubarLabel>
<MenubarItem>
<template #icon><FilePlus /></template>
New
<template #trailing><Kbd>Ctrl N</Kbd></template>
</MenubarItem>
<MenubarItem>
<template #icon><Copy /></template>
Duplicate
</MenubarItem>
<MenubarSeparator />
<MenubarItem tone="danger">
<template #icon><Trash2 /></template>
Delete
</MenubarItem>
</MenubarMenu>
</Menubar>
</template>
Checkbox and radio items
MenubarCheckboxItem is a toggle where several can be on at once and the menu stays open after toggling; wrap a set of mutually exclusive options in MenubarRadioGroup.
Word wrap on, line numbers off, font size medium
<script setup lang="ts">
import { ref } from 'vue'
import {
Menubar,
MenubarCheckboxItem,
MenubarLabel,
MenubarMenu,
MenubarRadioGroup,
MenubarRadioItem,
MenubarSeparator,
Stack,
Text,
} from '@hina-ui/vue'
const wrap = ref(true)
const numbers = ref(false)
const size = ref('md')
const names: Record<string, string> = { sm: 'small', md: 'medium', lg: 'large' }
</script>
<template>
<Stack gap="sm" align="start">
<Menubar label="View settings">
<MenubarMenu label="View">
<MenubarCheckboxItem v-model:checked="wrap">Word wrap</MenubarCheckboxItem>
<MenubarCheckboxItem v-model:checked="numbers">Line numbers</MenubarCheckboxItem>
<MenubarSeparator />
<MenubarLabel>Font size</MenubarLabel>
<MenubarRadioGroup v-model="size">
<MenubarRadioItem value="sm">Small</MenubarRadioItem>
<MenubarRadioItem value="md">Medium</MenubarRadioItem>
<MenubarRadioItem value="lg">Large</MenubarRadioItem>
</MenubarRadioGroup>
</MenubarMenu>
</Menubar>
<Text tone="muted" size="sm">
Word wrap {{ wrap ? 'on' : 'off' }}, line numbers {{ numbers ? 'on' : 'off' }}, font size
{{ names[size] }}
</Text>
</Stack>
</template>
Controlled
v-model binds the value of the open menu, an empty string when none is open; a menu can be opened from outside.
Open now: none
<script setup lang="ts">
import { ref } from 'vue'
import { Button, Inline, Menubar, MenubarItem, MenubarMenu, Stack, Text } from '@hina-ui/vue'
const open = ref('')
</script>
<template>
<Stack gap="md" align="start">
<Inline gap="sm">
<Button variant="outline" tone="neutral" @click="open = 'file'">Open "File"</Button>
<Button variant="outline" tone="neutral" @click="open = 'help'">Open "Help"</Button>
</Inline>
<Menubar v-model="open" label="Controlled menu">
<MenubarMenu label="File" value="file">
<MenubarItem>New</MenubarItem>
<MenubarItem>Open</MenubarItem>
</MenubarMenu>
<MenubarMenu label="Help" value="help">
<MenubarItem>Documentation</MenubarItem>
<MenubarItem>About</MenubarItem>
</MenubarMenu>
</Menubar>
<Text tone="muted" size="sm">Open now: {{ open || 'none' }}</Text>
</Stack>
</template>
Disabled menus
disabled on a MenubarMenu disables its trigger; disabled on an item only makes that item unselectable.
<script setup lang="ts">
import { Menubar, MenubarItem, MenubarMenu } from '@hina-ui/vue'
</script>
<template>
<Menubar label="Disabled example">
<MenubarMenu label="File">
<MenubarItem>New</MenubarItem>
<MenubarItem disabled>Save as</MenubarItem>
</MenubarMenu>
<MenubarMenu label="Debug" disabled>
<MenubarItem>Start debugging</MenubarItem>
</MenubarMenu>
</Menubar>
</template>
Behavior
- Clicking a trigger opens its menu and clicking again closes it; while a menu is open, moving the pointer onto another trigger switches to that menu.
- Left and right arrows move between triggers and switch menus while one is open; up and down move between items, Enter or Space picks one, and Esc closes the menu and returns focus to the trigger.
- The page keeps scrolling and stays interactive while a menu is open, which is what lets the pointer move between triggers; picking an item or clicking outside closes it.
Accessibility
- The root is
role="menubar", named throughlabel; triggers aremenuitems witharia-haspopup, and the open panel isrole="menu". - The triggers form a roving tab stop: Tab enters and leaves once, and the arrow keys move within.
API
Menubar
| Prop | Type | Default | Description |
|---|---|---|---|
label | string | — | Accessible name of the bar |
loop | boolean | true | Whether the arrow keys wrap around at the ends |
modelValue | string | — | value of the open menu, supports two-way binding |
class | string | — | Classes appended to the root element |
Items and other parts
MenubarItem, MenubarCheckboxItem, MenubarRadioGroup, MenubarRadioItem, MenubarGroup, MenubarLabel, MenubarSeparator and MenubarSub take exactly the props, slots and events of their DropdownMenu counterparts; see DropdownMenu.