Skip to content

Dropdown

A menu of grouped actions built on Reka UI's DropdownMenu primitive.

Usage

vue
<template>
  <SDropdown
    :items="[
      [{ label: 'Edit', icon: 'hugeicons:pencil' }, { label: 'Duplicate', icon: 'hugeicons:copy' }],
      [{ label: 'Delete', icon: 'hugeicons:delete-02' }],
    ]"
  >
    <SButton variant="outline">
      Open dropdown
    </SButton>
  </SDropdown>
</template>

Items are an array of groups — a separator is rendered between each group. Items can run onSelect actions or navigate through to (including external URLs, with target/rel when needed). shortcut renders a Kbd hint; set hotkey: true to bind it while the dropdown is open.

Groups and separators

items is a DropdownItem[][]: each inner array is one group. Selaras adds a separator between groups; items in the same group have no separator between them. There is no separate type: 'separator' item in this API. Split the items into another inner array wherever a boundary belongs.

No action selected

vue
<script setup lang="ts">
const lastAction = ref('')
const profile = { label: 'Profile', onSelect: () => lastAction.value = 'Profile selected' }
const signOut = { label: 'Sign out', onSelect: () => lastAction.value = 'Sign out selected' }
</script>

<template>
  <!-- Two groups: a separator appears between Profile and Sign out. -->
  <SDropdown :items="[[profile], [signOut]]">
    <SButton variant="outline">
      Two groups
    </SButton>
  </SDropdown>

  <!-- One group: no separator appears between the two items. -->
  <SDropdown :items="[[profile, signOut]]">
    <SButton variant="outline">
      One group
    </SButton>
  </SDropdown>
</template>

Use ui.separator to change the separator's styling. This applies to the anchored menu and the adaptive mobile modal:

vue-html
<SDropdown :items="[[profile], [signOut]]" :ui="{ separator: 'my-2' }">
  <SButton>Account</SButton>
</SDropdown>

shortcut only displays the hint. Add hotkey: true to activate the item with that key while this dropdown is open. Use mod for the platform's primary modifier (⌘ on macOS, Ctrl elsewhere). Items with to navigate as links by mouse, keyboard selection, or shortcut; action items call onSelect through the same selection path.

vue
<script setup lang="ts">
const lastAction = ref('')
const items = [[
  { label: 'Save draft', icon: 'hugeicons:floppy-disk', shortcut: 'mod+s', hotkey: true, onSelect: () => lastAction.value = 'Saved draft' },
  { label: 'Preview', shortcut: 'mod+p', onSelect: () => lastAction.value = 'Preview opened' },
  { label: 'Documentation', to: '/components/overlays/context-menu', shortcut: 'mod+shift+d', hotkey: true },
]]
</script>

<template>
  <SDropdown :items="items">
    <SButton variant="outline">
      Open actions
    </SButton>
  </SDropdown>
</template>

In this example, Save draft and Documentation respond to their shortcuts; Preview shows a hint only. Open the dropdown before pressing a menu shortcut.

Custom item content

The item slot replaces an item's plain-text label with anything, scoped with item, so a single template can vary per item. The component continues to render shortcut after the slot content automatically:

vue-html
<SDropdown :items="items">
  <template #item="{ item }">
    <span class="flex-1">{{ item.label }}</span>
  </template>
  ...
</SDropdown>

Destructive items

destructive: true styles an item for a delete/remove-style action (danger text, danger-tinted hover) - just this one flag rather than a full color choice, since a menu item realistically only ever needs this one special case:

vue-html
<SDropdown
  :items="[
    [{ label: 'Edit', icon: 'hugeicons:pencil' }, { label: 'Duplicate', icon: 'hugeicons:copy' }],
    [{ label: 'Delete', icon: 'hugeicons:delete-02', destructive: true }],
  ]"
>
  ...
</SDropdown>

Arrow

arrow shows a small pointer triangle connecting the menu to its trigger. Pass an object to configure its width, height, rounded tip, or corner clearance (padding):

vue-html
<SDropdown :items="items" arrow>...</SDropdown>
<SDropdown :items="items" :arrow="{ width: 16, height: 8, rounded: true, padding: 12 }">...</SDropdown>

Adaptive mobile presentation

Set adaptive to show the action list in a modal on small screens while keeping the anchored menu on larger screens. This is opt-in; links, action handlers, groups, item slots, and shortcut hints work in either presentation. The modal uses normal buttons and links, so touch users get larger targets and keyboard users can move through actions with Tab. Escape and the backdrop close it and return focus to the trigger.

Accessibility

Dropdown renders Reka UI's DropdownMenu primitive, so the accessibility semantics come from there rather than being reimplemented here: the trigger exposes aria-haspopup/aria-expanded, the menu itself is role="menu" with each item as role="menuitem", ↑/↓ move between items (wrapping past either end), typing a letter jumps to the next matching item, and Enter/Space selects the highlighted one - Escape or an outside click closes the menu and returns focus to the trigger. With adaptive on a small screen, the action list becomes a dialog containing native links and buttons; focus follows the dialog's normal Tab order instead of menu arrow-key navigation.

Custom :ui

To see exactly what you'd be overriding - the current default classes for every slot and variant - here's the menu's own theme file:

Theme source "dropdown" not found

Positioning and portal

Use positioning for the menu's side, alignment, offsets, and collision behavior. portal defaults to the document body; set it to false to render inline or pass a CSS selector or HTMLElement as the teleport target. ui.content remains the styling override.

vue-html
<SDropdown :items="items" :positioning="{ side: 'top', align: 'end' }" portal="#overlay-root">...</SDropdown>

Props

For the items array shape, see Groups and separators. For link items, use to with optional target and rel. Shortcut hints are display-only unless hotkey: true is set.

PropTypeDefault
items{ label: string; icon?: string; disabled?: boolean; destructive?: boolean; onSelect?: () => void; to?: string; target?: string; rel?: string; shortcut?: string; hotkey?: boolean }[][]-
arrowboolean | RoundedArrowConfigfalse
positioningOverlayPositioningmenu defaults
portalboolean | string | HTMLElementtrue (document body)
adaptivebooleanfalse
uiPartial<Record<'content' | 'item' | 'icon' | 'separator' | 'arrow' | 'mobileList' | 'mobileGroup', string | object>>-

A class passed to <SDropdown> is merged into its default-slot trigger element. Use ui.content to style the floating menu.

Slots

SlotPropsDescription
default-The trigger element
item{ item }Replaces an item's label content
Edit this page