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 buttonitems(MenuItem[], required) - Array of menu itemsid(string, optional) - Unique ID for the dropdown. Auto-generated if not providedclass(string, optional) - Additional CSS classesposition('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 itemvalue(string, optional) - Value passed to onClick handlerhref(string, optional) - If provided, renders as a link instead of buttononClick(string, optional) - Name of global function to call when clicked (must be available on window)disabled(boolean, optional) - Whether the item is disabledseparator(boolean, optional) - If true, renders as a separator linesubmenu(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.