Menu Button
Open a command menu from one trigger and restore focus on dismissal.
Usage
Action menu
Create or import workspace resources from one compact trigger.
Create or import workspace resources from one compact trigger.
<!-- Menu Button / Actions -->
<script setup lang="ts">
import { MenuButtonRoot, MenuButtonTrigger, MenuButtonContent, MenuItem, MenuSeparator } from '@sectile/vue/menu-button'
import { ref } from 'vue'
const lastAction = ref('')
const items = [
{ id: 'new-file', parentID: null },
{ id: 'new-folder', parentID: null },
{ id: 'import', parentID: null },
]
</script>
<template>
<MenuButtonRoot :items="items" @invoke="lastAction = $event">
<MenuButtonTrigger>Create</MenuButtonTrigger>
<MenuButtonContent>
<MenuItem value="new-file">New file <kbd>⌘N</kbd></MenuItem>
<MenuItem value="new-folder">New folder <kbd>⇧⌘N</kbd></MenuItem>
<MenuSeparator />
<MenuItem value="import">Import…</MenuItem>
</MenuButtonContent>
</MenuButtonRoot>
<p v-if="lastAction" role="status">{{ lastAction }} invoked</p>
</template>Nested submenu
Keep secondary export formats inside the Export as command.
Keep secondary export formats inside the Export as command.
<!-- Menu Button / Nested -->
<script setup lang="ts">
import { MenuButtonRoot, MenuButtonTrigger, MenuButtonContent, MenuItem, MenuSeparator, MenuSubContent } from '@sectile/vue/menu-button'
import { ref } from 'vue'
const lastAction = ref('')
const items = [
{ id: 'download', parentID: null },
{ id: 'share', parentID: null },
{ id: 'export', parentID: null },
{ id: 'pdf', parentID: 'export' },
{ id: 'markdown', parentID: 'export' },
{ id: 'csv', parentID: 'export' },
]
</script>
<template>
<MenuButtonRoot :items="items" @invoke="lastAction = $event">
<MenuButtonTrigger>Export options</MenuButtonTrigger>
<MenuButtonContent>
<MenuItem value="download">Download copy <kbd>⇧D</kbd></MenuItem>
<MenuItem value="share">Share link <kbd>⌘L</kbd></MenuItem>
<MenuSeparator />
<MenuItem value="export">Export as ›</MenuItem>
<MenuSubContent for="export">
<MenuItem value="pdf">PDF document</MenuItem>
<MenuItem value="markdown">Markdown</MenuItem>
<MenuItem value="csv">CSV data</MenuItem>
</MenuSubContent>
</MenuButtonContent>
</MenuButtonRoot>
<p v-if="lastAction" role="status">{{ lastAction }} invoked</p>
</template>Controlled
Let the parent own the current value and apply accepted changes back to the component.
Let the parent own the current value and apply accepted changes back to the component.
<!-- Menu Button / Controlled -->
<script setup lang="ts">
import { MenuButtonRoot, MenuButtonTrigger, MenuButtonContent, MenuItem } from '@sectile/vue/menu-button'
import { ref } from 'vue'
const open = ref(false)
const items = [{ id: 'duplicate', parentID: null }, { id: 'archive', parentID: null }]
</script>
<template>
<MenuButtonRoot v-model:open="open" :items="items">
<MenuButtonTrigger>{{ open ? 'Close actions' : 'Open actions' }}</MenuButtonTrigger>
<MenuButtonContent>
<MenuItem v-for="item in items" :key="item.id" :value="item.id">
{{ item.id }}
</MenuItem>
</MenuButtonContent>
</MenuButtonRoot>
</template>Floating positioning
This component uses the shared positioning engine. Use the live positioning example to change side, align, offsets, collision boundaries, strategy, and tracking while inspecting the resolved placement.
API
Vue package: @sectile/vue/menu-button
MenuButtonContentMenuButtonRootMenuButtonTriggerMenuItemMenuSeparatorMenuSubContent
Props
MenuButtonRootProps
align- Type
PositionAlignDefaultundefinedAlignment of positioned content relative to its anchor.
as- Type
PrimitiveAsDefaultundefinedElement or component rendered for this part.
asChild- Type
booleanDefaultundefinedWhether to merge this part into its single child instead of rendering a wrapper.
avoidCollisions- Type
booleanDefaultundefinedWhether positioned content may flip or shift to remain visible.
collisionBoundary- Type
PositionBoundaryDefaultundefinedBoundary used to keep positioned content visible.
collisionPadding- Type
PositionPaddingDefaultundefinedSpace kept between positioned content and its collision boundary.
defaultHighlightedValue- Type
string | nullDefaultundefinedInitially highlighted value for uncontrolled state.
defaultOpen- Type
booleanDefaultundefinedInitial uncontrolled open state.
disabled- Type
booleanDefaultundefinedWhether interaction is unavailable.
disabledItems- Type
readonly string[]DefaultundefinedItem values excluded from focus and selection.
hideWhenDetached- Type
booleanDefaultundefinedWhether positioned content hides when its anchor leaves the layout.
items- Type
readonly MenuItemDefinition<string>[]DefaultRequiredOrdered item values managed by the component.
label- Type
stringDefaultundefinedAccessible name announced for the control.
open- Type
booleanDefaultundefinedWhether the associated popup or disclosure is open.
policies- Type
MenuPolicies<string>DefaultundefinedBehavior policies that customize validation, movement, or selection.
position- Type
booleanDefaultundefinedWhether the popup is positioned relative to its trigger.
side- Type
PositionSideDefaultundefinedPreferred side of the anchor for positioned content.
sideOffset- Type
numberDefaultundefinedDistance between positioned content and its anchor.
strategy- Type
PositionStrategyDefaultundefinedCSS positioning strategy used for anchored content.
textValue- Type
(id: string) => stringDefaultundefinedReturns searchable or presentational text for an item value.
tracking- Type
PositionTrackingDefaultundefinedUpdate strategy used while anchored content is open.
MenuItemProps
as- Type
PrimitiveAsDefaultundefinedElement or component rendered for this part.
asChild- Type
booleanDefaultundefinedWhether to merge this part into its single child instead of rendering a wrapper.
disabled- Type
booleanDefaultundefinedWhether interaction is unavailable.
value- Type
stringDefaultRequiredCurrent value exposed by this contract.
MenuPartProps
as- Type
PrimitiveAsDefaultVaries by partElement or component rendered for this part.
asChild- Type
booleanDefaultfalseWhether to merge this part into its single child instead of rendering a wrapper.
MenuSubContentProps
as- Type
PrimitiveAsDefaultundefinedElement or component rendered for this part.
asChild- Type
booleanDefaultundefinedWhether to merge this part into its single child instead of rendering a wrapper.
for- Type
stringDefaultRequiredValue or ID of the related part targeted by this component.
Slots
MenuItemSlotProps
disabled- Type
booleanWhether interaction is unavailable.
highlighted- Type
booleanWhether this item is highlighted for interaction.
open- Type
booleanWhether the associated popup or disclosure is open.
value- Type
stringCurrent value exposed by this contract.
MenuRootSlotProps
disabled- Type
booleanWhether interaction is unavailable.
highlightedValue- Type
string | nullValue currently highlighted for interaction.
open- Type
booleanWhether the associated popup or disclosure is open.
openPath- Type
readonly string[]Ordered values in the open menu path.
Events
MenuButtonRoot
invoke- Payload
stringEmitted when the current action is invoked.
update:open- Payload
booleanEmitted when the component requests a new open state.
Parts
Shared scope: [data-scope="menu-button"]. Combine it with a part selector to keep styles local to this component.
| Part | Selector | Role | Extra attributes |
|---|---|---|---|
trigger | [data-part="trigger"] | Opens, closes, or activates the associated content. | — |
content | [data-part="content"] | Contains the component content shown for the active state. | — |
item | [data-part="item"] | Menu item at any hierarchy depth; use data-level to distinguish top-level and nested items | data-scope="menu"data-level="<depth>" |
sub-content | [data-part="sub-content"] | Popup content owned by a parent menu item | data-scope="menu"data-level="<depth>" |
separator | [data-part="separator"] | Separates related groups without becoming an action. | data-scope="menu" |
Keyboard interaction
| Key | Behavior |
|---|---|
| Arrow Up / Arrow Down | Move between items at the current menu level. |
| Arrow Right / Arrow Left | Open a submenu or return to its parent. |
| Home / End | Move to the first or last item at the current level. |
| Enter / Space | Open a submenu or invoke the current item. |
| Escape | Close the current menu level. |
Accessibility
The trigger exposes popup and expanded state; the opened content uses menu semantics and returns focus on close.
See the corresponding WAI-ARIA pattern for the host accessibility contract.
