DropdownMenu
DropdownMenu is a set of four composable pieces — root, trigger, content, and item — built on reka-ui's menu primitive. The root coordinates open state; the trigger opens the menu; the content is portalled; and each item emits select.
<script setup lang="ts">
import {
Button,
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from '@myghf/ui'
function doEdit() {}
function doDelete() {}
</script>
<template>
<DropdownMenu>
<DropdownMenuTrigger>
<Button variant="outline">Actions</Button>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem @select="doEdit">Edit</DropdownMenuItem>
<DropdownMenuItem variant="destructive" @select="doDelete">Delete</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</template>Examples
Basic menu
Compose the trigger with a real control (here, Button). Use variant="destructive" on items that remove or destroy something.
<script setup lang="ts">
import { ref } from 'vue'
import {
Button,
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from '@myghf/ui'
const last = ref('none')
function choose(label: string) {
last.value = label
}
</script>
<template>
<ClientOnly>
<DropdownMenu>
<DropdownMenuTrigger>
<Button variant="outline">Actions</Button>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem @select="choose('Edit')">Edit</DropdownMenuItem>
<DropdownMenuItem @select="choose('Duplicate')">Duplicate</DropdownMenuItem>
<DropdownMenuItem variant="destructive" @select="choose('Delete')">Delete</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
<p class="mt-3 text-sm text-muted">Last action: {{ last }}</p>
<template #fallback>
<span class="text-sm text-muted">Loading menu…</span>
</template>
</ClientOnly>
</template>Alignment and offset
align chooses how the content lines up with the trigger (start / center / end), and sideOffset sets the gap in pixels.
<script setup lang="ts">
import {
Button,
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from '@myghf/ui'
const aligns = ['start', 'center', 'end'] as const
</script>
<template>
<ClientOnly>
<div class="flex flex-wrap gap-2">
<DropdownMenu v-for="a in aligns" :key="a">
<DropdownMenuTrigger>
<Button variant="outline" size="sm">{{ a }}</Button>
</DropdownMenuTrigger>
<DropdownMenuContent :align="a" :side-offset="6">
<DropdownMenuItem>Profile</DropdownMenuItem>
<DropdownMenuItem>Settings</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
<template #fallback>
<span class="text-sm text-muted">Loading menu…</span>
</template>
</ClientOnly>
</template>Props
DropdownMenu
The root declares no props of its own. Extra attributes — including reka's open, defaultOpen, modal, and dir — fall through to DropdownMenuRoot via $attrs. Use v-model:open for a controlled menu.
DropdownMenuTrigger
No props. It renders reka's trigger through as-child with focus-on-select enabled, so the single element in its default slot becomes the trigger and regains focus after a selection.
DropdownMenuContent
| Prop | Type | Default | Description |
|---|---|---|---|
align | 'start' | 'center' | 'end' | 'start' | Alignment of the content relative to the trigger. |
side | 'top' | 'right' | 'bottom' | 'left' | 'bottom' | Side the content is placed on. |
sideOffset | number | 4 | Distance between the trigger and the content, in pixels. |
The content renders inside reka's portal, so it is appended to <body> rather than to the component's place in the DOM.
DropdownMenuItem
| Prop | Type | Default | Description |
|---|---|---|---|
variant | 'default' | 'destructive' | 'default' | destructive colours the label with the error token to signal a risky action. |
Events
| Event | On | Payload | Description |
|---|---|---|---|
update:open | DropdownMenu | boolean | Emitted when the menu opens or closes. |
select | DropdownMenuItem | — | Emitted when the item is chosen (pointer or keyboard). |
Slots
| Component | Slot | Description |
|---|---|---|
DropdownMenu | default | The trigger and content. |
DropdownMenuTrigger | default | The single trigger element. |
DropdownMenuContent | default | The menu items. |
DropdownMenuItem | default | The item's content (text, icon, shortcut hint). |
There are no named slots.
Exposed methods
None. No part of DropdownMenu calls defineExpose; drive state with v-model:open and react to select on the items.
Accessibility
- reka-ui supplies the menu semantics: the trigger exposes
aria-haspopup="menu"andaria-expanded, and the content is arole="menu"with roving focus. Arrow keys move between items, typeahead jumps to an item by its text, and Esc closes the menu and returns focus to the trigger. DropdownMenuTriggersetsfocus-on-select, so focus lands back on the trigger after an item is chosen — important for keyboard users.- Name the trigger.
DropdownMenuTriggerrenders no visible label of its own; when the slot is icon-only, give it anaria-label:vue<DropdownMenuTrigger> <Button size="icon" aria-label="More actions"><Icon name="ellipsis" /></Button> </DropdownMenuTrigger> variant="destructive"only changes the colour. Reinforce the meaning in the label ("Delete") and add a confirmation step for irreversible actions.- Items are menu items, not links. For navigation, handle
selectand route manually (or render your own anchor inside the item and keep the menu open semantics in mind).
Dark mode & RTL
- The content uses semantic tokens (
bg-surface,border-border,text-foreground,shadow-popover); highlighted and disabled items usesurface-mutedand opacity, so both themes are covered. - Item layout is logical: an inline-flex row with
gap-2and symmetric padding, so it needs no RTL override.DropdownMenuContent'salignandsideare resolved by reka against the trigger, so placement follows the document direction.