Navigation & Menus
Menu
A dropdown list of actions opened from a trigger, with full keyboard navigation.
Install
import { Menu } from 'vael-ui'Playground
Fruits
bottom
start
import { Menu } from 'vael-ui'
<Menu side="bottom" align="start" :sideOffset="8" :alignOffset="0" closeOnEsc closeOnOutside :forceMount="false" scrollFade :items="items">Menu</Menu>Props
| Name | Type | Default | Description |
|---|---|---|---|
items | readonly MenuEntry<T>[] | undefined | Data-driven rows. Ignored when the default slot renders custom markup instead. | |
triggerEl | TriggerRef | External ref for a trigger that can't live in the #trigger slot. | |
side | "top" | "right" | "bottom" | "left" | "bottom" | Which side of the trigger the panel opens on. |
align | "start" | "end" | "center" | "start" | How the panel aligns against the trigger along that side. |
sideOffset | number | undefined | 8 | Gap between the trigger and the panel, in pixels. |
alignOffset | number | undefined | 0 | Shifts the panel along the alignment axis, in pixels. |
closeOnEsc | boolean | undefined | true | Escape key closes the panel. |
closeOnOutside | boolean | undefined | true | Clicking outside the panel closes it. |
beforeClose | ((done: () => void) => void) | undefined | Custom exit animation; call `done()` when it's complete. Delays the actual close/unmount until then. | |
forceMount | boolean | undefined | false | When true, presence is v-show-driven and owned by the consumer (e.g. AnimatePresence). |
teleportTo | string | HTMLElement | undefined | "body" | CSS selector or an actual DOM element — same contract as Vue's own Teleport `to`. |
scrollFade | boolean | undefined | true | Masks the panel's top/bottom edge as its content scrolls under it, signaling there's more. |
maxPanelHeight | number | undefined | Caps the panel's height at this many pixels even when the viewport has room for more — the item list scrolls internally past it instead of the panel growing indefinitely. Omitted keeps today's behavior (only the viewport limits it). | |
ui | Partial<{ positioner: UiPartValue; panel: UiPartValue; header: UiPartValue; footer: UiPartValue; }> | undefined | Per-instance part-class/style overrides. | |
open | boolean | undefined | false | Whether the menu is open. |
Slots
| Name | Type | Description |
|---|---|---|
trigger | { open: boolean; } | Co-located trigger markup — Menu wires the click and anchors to it; render just the button. |
header | any | Above the item list, outside its scroll region — stays put while `items` scrolls underneath. |
item | { item: T; } | Override one data-driven row's content while keeping its behavior. |
default | { close: () => void; open: boolean; isClosing: boolean; cancelClose: () => void; panelEl: HTMLElement | null; placement: string; maxHeight: number | null; } | Fully custom menu content — render your own role="menuitem" markup; `items` is ignored. |
footer | any | Below the item list, outside its scroll region — stays put while `items` scrolls above it. |
Events
| Name | Type | Description |
|---|---|---|
open-change | [value: boolean, details: PopoverOpenChangeDetails] | |
select | [item: T] | |
collapse | [] | |
active | [value: boolean] | |
update:open | [value: boolean] |
Exposed
| Name | Type | Description |
|---|---|---|
panelEl | unknown | Type inference unavailable — vue-component-meta cannot resolve defineExpose on this generic component. |
positionerEl | unknown | Type inference unavailable — vue-component-meta cannot resolve defineExpose on this generic component. |
listEl | unknown | Type inference unavailable — vue-component-meta cannot resolve defineExpose on this generic component. |
placement | unknown | Type inference unavailable — vue-component-meta cannot resolve defineExpose on this generic component. |
positionerStyle | unknown | Type inference unavailable — vue-component-meta cannot resolve defineExpose on this generic component. |
isClosing | unknown | Type inference unavailable — vue-component-meta cannot resolve defineExpose on this generic component. |
open | unknown | Type inference unavailable — vue-component-meta cannot resolve defineExpose on this generic component. |
toggle | unknown | Type inference unavailable — vue-component-meta cannot resolve defineExpose on this generic component. |
close | unknown | Type inference unavailable — vue-component-meta cannot resolve defineExpose on this generic component. |
cancelClose | unknown | Type inference unavailable — vue-component-meta cannot resolve defineExpose on this generic component. |