Dropdown Component — Astro

Accessible dropdown menu component with keyboard navigation and menu items

Dropdown Component

An accessible dropdown menu component for displaying lists of actions or options. Supports menu items, separators, links, and custom click handlers.

Basic Usage

Live Example
astro astro
---
import Dropdown from '../../components/Dropdown.astro';
---

<Dropdown 
  trigger="Actions"
  items={[
    { label: 'Edit', value: 'edit', onClick: 'handleAction' },
    { label: 'Delete', value: 'delete', onClick: 'handleAction' },
    { separator: true },
    { label: 'Settings', href: '/settings' },
  ]}
/>

With Links

Live Example
astro astro
<Dropdown 
  trigger="Navigate"
  items={[
    { label: 'Home', href: '/' },
    { label: 'Documentation', href: '/docs' },
    { label: 'Components', href: '/docs/components' },
    { separator: true },
    { label: 'GitHub', href: 'https://github.com' },
  ]}
/>

With Disabled Items

Live Example
astro astro
<Dropdown 
  trigger="Options"
  items={[
    { label: 'Available Option', value: 'available' },
    { label: 'Disabled Option', value: 'disabled', disabled: true },
    { separator: true },
    { label: 'Another Option', value: 'another' },
  ]}
/>

Nested Menus

Live Example
astro astro
<Dropdown 
  trigger="Actions"
  items={[
    { 
      label: 'Edit', 
      submenu: [
        { label: 'Cut', value: 'cut' },
        { label: 'Copy', value: 'copy' },
        { label: 'Paste', value: 'paste' },
        { separator: true },
        { 
          label: 'Advanced', 
          submenu: [
            { label: 'Find & Replace', value: 'find-replace' },
            { label: 'Go to Line', value: 'go-to-line' },
            { label: 'Format Document', value: 'format' },
          ]
        },
      ]
    },
    { 
      label: 'View', 
      submenu: [
        { label: 'Zoom In', value: 'zoom-in' },
        { label: 'Zoom Out', value: 'zoom-out' },
        { label: 'Reset Zoom', value: 'reset-zoom' },
        { separator: true },
        { 
          label: 'Appearance', 
          submenu: [
            { label: 'Theme', value: 'theme' },
            { label: 'Font Size', value: 'font-size' },
            { label: 'Word Wrap', value: 'word-wrap' },
            { separator: true },
            { label: 'Show Line Numbers', value: 'line-numbers' },
            { label: 'Show Minimap', value: 'minimap' },
          ]
        },
        { separator: true },
        { label: 'Full Screen', value: 'fullscreen' },
      ]
    },
    { 
      label: 'Tools', 
      submenu: [
        { label: 'Command Palette', value: 'command-palette' },
        { label: 'Terminal', value: 'terminal' },
        { separator: true },
        { 
          label: 'Extensions', 
          submenu: [
            { label: 'Install Extension', value: 'install-ext' },
            { 
              label: 'Manage Extensions', 
              submenu: [
                { label: 'Installed', value: 'installed' },
                { label: 'Recommended', value: 'recommended' },
                { label: 'Popular', value: 'popular' },
                { separator: true },
                { label: 'Browse Marketplace', href: '/extensions' },
              ]
            },
            { separator: true },
            { label: 'Update All', value: 'update-all' },
          ]
        },
        { label: 'Settings', value: 'settings' },
      ]
    },
    { separator: true },
    { label: 'Help', href: '/docs' },
    { label: 'Settings', href: '/settings' },
  ]}
/>

Menu Positioning

Live Examples

Menu aligns to left edge

Menu aligns to right edge

astro astro
<!-- Menu aligns to left edge of trigger (default) -->
<Dropdown 
  trigger="Left Position"
  position="left"
  items={[...]}
/>

<!-- Menu aligns to right edge of trigger -->
<Dropdown 
  trigger="Right Position"
  position="right"
  items={[...]}
/>

Props

  • trigger (string, required) - Text displayed on the trigger button
  • items (MenuItem[], required) - Array of menu items
  • id (string, optional) - Unique ID for the dropdown. Auto-generated if not provided
  • class (string, optional) - Additional CSS classes
  • position ('left' | 'right', optional) - Menu position relative to trigger (default: 'left')
  • align ('start' | 'end', optional) - Menu alignment within position (default: 'start')

MenuItem Interface

  • label (string, required) - Display text for the menu item
  • value (string, optional) - Value passed to onClick handler
  • href (string, optional) - If provided, renders as a link instead of button
  • onClick (string, optional) - Name of global function to call when clicked (must be available on window)
  • disabled (boolean, optional) - Whether the item is disabled
  • separator (boolean, optional) - If true, renders as a separator line
  • submenu (MenuItem[], optional) - Array of menu items for nested submenu

Features

  • Full keyboard navigation (Arrow keys, Enter, Space, Escape, Home, End, Tab)
  • Nested submenus with click-to-open and keyboard support (ArrowRight/ArrowLeft)
  • Supports up to 3 levels of nested menus with proper parent menu preservation
  • Submenu items properly handle clicks and close parent menu
  • Accessible ARIA attributes (role="menu", role="menuitem", aria-expanded, aria-haspopup, aria-label)
  • All menu items have accessible names via aria-label attributes for screen readers
  • WCAG AA compliant touch targets (minimum 2.5rem/40px height)
  • No horizontal scrolling - submenus appear directly under parent items
  • Menus expand to show all items without vertical scrollbars
  • Smart submenu closing - only closes siblings at the same level, preserves parent menus
  • Outside click to close
  • Focus management (returns to trigger on close)
  • Menu items can be links or buttons
  • Separator support for grouping items
  • Disabled item support
  • Positioning options (left/right alignment)
  • Theme-aware styling using semantic variables
  • Smooth animations (respects prefers-reduced-motion)
  • Mobile responsive

Keyboard Navigation

  • Enter or Space - Open/close menu or activate item
  • ArrowDown - Open menu and focus first item, or navigate down
  • ArrowUp - Open menu and focus last item, or navigate up
  • ArrowRight - Open submenu (if available) and focus first submenu item
  • ArrowLeft - Close submenu and return focus to parent item
  • Home - Jump to first item
  • End - Jump to last item
  • Escape - Close menu
  • Tab - Close menu and continue tabbing

Accessibility

  • Uses proper ARIA menu pattern (role="menu", role="menuitem")
  • All menu items have accessible names via aria-label attributes
  • Keyboard accessible with full navigation support
  • Focus trapping within menu when open
  • Screen reader compatible with proper ARIA attributes
  • Disabled items are properly marked with aria-disabled
  • Submenu state properly communicated via aria-expanded and aria-haspopup
  • WCAG AA compliant with proper contrast ratios and touch targets

Custom Click Handlers

To use custom click handlers, define a global function and reference it by name:

astro astro
<script is:inline>
  window.handleAction = function(value) {
    // console.log('Selected:', value);
    // Your custom logic here
  };
</script>

<Dropdown 
  trigger="Actions"
  items={[
    { label: 'Edit', value: 'edit', onClick: 'handleAction' },
    { label: 'Delete', value: 'delete', onClick: 'handleAction' },
  ]}
/>

Svelte & Vanilla: Svelte ¡ Vanilla: same HTML and BEM as in Usage above; add minimal JS for open/close and keyboard.