Skip to content

Dropdown

Dropdown component that looks like a button but displays a list of options when clicked. Each option can be a link (if it contains an href) or trigger an action. Compliant with W3C accessibility standards with complete keyboard and screen reader support.

Usage examples

Basic Dropdown

Simple user menu

vue
<script setup>
import { UserIcon, CogIcon, ArrowUturnLeftIcon } from '@heroicons/vue/24/outline'

const userOptions = [
  { value: 'profile', label: 'My profile', icon: UserIcon },
  { value: 'settings', label: 'Settings', icon: CogIcon },
  { value: 'separator1', separator: true },
  { value: 'logout', label: 'Logout', icon: ArrowUturnLeftIcon }
]

const handleSelect = (option) => {
  console.log('Selected option:', option)
  // Handle action based on option
}
</script>

<template>
  <SuDropdown 
    :options="userOptions"
    label="User menu"
    :icon="UserIcon"
    @select="handleSelect"
  />
</template>

Mixed options (links + actions)

vue
<script setup>
import { UserIcon, DocumentIcon, CogIcon, QuestionMarkCircleIcon } from '@heroicons/vue/24/outline'

const mixedOptions = [
  { value: 'profile', label: 'My profile', icon: UserIcon },
  { value: 'docs', label: 'Documentation', href: '/docs', icon: DocumentIcon },
  { value: 'separator1', separator: true },
  { value: 'settings', label: 'Settings', icon: CogIcon },
  { value: 'help', label: 'Help', href: 'https://help.example.com', target: '_blank', icon: QuestionMarkCircleIcon }
]

const handleSelect = (option) => {
  // Options with href navigate automatically
  // Options without href trigger this function
  if (!option.href) {
    console.log('Action:', option.value)
  }
}
</script>

<template>
  <SuDropdown 
    :options="mixedOptions"
    label="Mixed menu"
    :icon="CogIcon"
    variant="secondary"
    @select="handleSelect"
  />
</template>

API

Props

PropTypeDefaultDescription
optionsDropdownOption[][]List of dropdown options
trigger'click' | 'hover''click'Opening trigger
placement'bottom-start' | 'bottom-end' | 'top-start' | 'top-end' | 'left-start' | 'left-end' | 'right-start' | 'right-end''bottom-start'Menu position
size'sm' | 'md' | 'lg''md'Button size
variant'primary' | 'secondary' | 'outline' | 'ghost''outline'Button variant
disabledbooleanfalseDisable the dropdown
loadingbooleanfalseLoading state
iconComponentundefinedButton icon
iconDisplay'left' | 'right' | 'only''left'Icon position
labelstringundefinedButton label
closeOnSelectbooleantrueClose after selection
maxHeightstring'300px'Maximum menu height

Option types

typescript
interface DropdownOption {
  value: string | number
  label: string
  href?: string                    // If present, option becomes a link
  target?: '_blank' | '_self' | '_parent' | '_top'
  rel?: string
  disabled?: boolean
  icon?: Component
  description?: string
  separator?: boolean              // If true, displays a separator
}

Events

EventTypeDescription
@select(option: DropdownOption) => voidEmitted when an option is selected
@open() => voidEmitted when menu opens
@close() => voidEmitted when menu closes
@focus(event: FocusEvent) => voidEmitted when button receives focus
@blur(event: FocusEvent) => voidEmitted when button loses focus

The Dropdown component automatically handles two types of options:

Options with an href property become links:

  • Automatic navigation to URL
  • External link support with target="_blank"
  • Automatic security attributes (rel="noopener noreferrer")
  • Automatic external icon for external links
vue
<!-- Internal link option -->
{ value: 'docs', label: 'Documentation', href: '/docs' }

<!-- External link option -->
{ value: 'github', label: 'GitHub', href: 'https://github.com', target: '_blank' }

⚡ Options with actions

Options without href trigger the @select event:

  • Event emission with complete option
  • Automatic menu closure (if closeOnSelect is true)
  • Custom handling in parent component
vue
<!-- Action option -->
{ value: 'delete', label: 'Delete', icon: TrashIcon }

<SuDropdown @select="handleAction" />

<script setup>
const handleAction = (option) => {
  if (option.value === 'delete') {
    // Deletion logic
  }
}
</script>

Accessibility

The Dropdown component follows WCAG 2.1 AA standards:

✅ Accessibility features

  • ARIA roles: role="button" for trigger, role="menu" for menu, role="menuitem" for options
  • Keyboard navigation: Support for arrow keys, Enter, Space, Escape, Home/End
  • Focus trap: Focus management in open menu
  • Voice announcements: Messages for screen readers during selections
  • ARIA states: aria-expanded, aria-haspopup, aria-controls
  • Accessible labels: Support for aria-label and aria-describedby
  • Visible focus: Clear and contrasted focus indicators
  • Color contrast: WCAG AA compliant ratios
  • Minimum sizes: Respects minimum touch target sizes
  • Dark mode: Automatically adapted contrast
  • High contrast: Support for prefers-contrast: high
  • Reduced motion: Respects prefers-reduced-motion

🎯 Best practices

vue
<!-- Dropdown with accessible label -->
<SuDropdown 
  :options="options"
  label="Actions menu"
  aria-label="Available actions menu"
  @select="handleSelect"
/>

<!-- Dropdown with icon only (REQUIRED: aria-label) -->
<SuDropdown 
  :options="options"
  :icon="EllipsisVerticalIcon"
  iconDisplay="only"
  aria-label="Options menu"
  @select="handleSelect"
/>

<!-- Dropdown with description -->
<SuDropdown 
  :options="options"
  label="Actions"
  aria-describedby="actions-help"
  @select="handleSelect"
/>
<div id="actions-help">Menu of available actions for this item</div>

Keyboard navigation

KeyAction
TabNavigate to/from dropdown
Enter / SpaceOpen/close menu, select focused option
Arrow DownOpen menu or navigate to next option
Arrow UpNavigate to previous option
HomeGo to first option
EndGo to last option
EscapeClose menu

Publié sous licence MIT.