ForgeDropdown

September 1, 2025 ยท View on GitHub

Advanced dropdown menu component with nested menus, keyboard navigation, and flexible item types.

Overview

The ForgeDropdown component provides a rich dropdown menu experience with support for nested menus, grouped items, checkboxes, radio buttons, and comprehensive keyboard navigation. It includes smart positioning and accessibility features.

Key Features

  • Nested Menus: Support for multi-level dropdown hierarchies
  • Multiple Item Types: Default, checkbox, radio, and divider items
  • Keyboard Navigation: Full arrow key, Enter, and Escape support
  • Smart Positioning: Auto-positioning to prevent viewport overflow
  • Flexible Triggers: Customizable trigger content and styling
  • Group Support: Organized menu items with group headers
  • Accessibility: Complete ARIA support and screen reader compatibility
  • AI-Ready: Comprehensive AI metadata for intelligent interactions

Basic Usage

<forge-dropdown
  label="Options"
  .items="${[
    { id: '1', label: 'Option 1', value: 'opt1' },
    { id: '2', label: 'Option 2', value: 'opt2' },
    { id: '3', label: 'Option 3', value: 'opt3', disabled: true }
  ]}"
>
</forge-dropdown>

Advanced Usage

<forge-dropdown
  label="Advanced Menu"
  variant="primary"
  size="large"
  position="auto"
  .closeOnSelect="${false}"
  .multiSelect="${true}"
  .items="${complexMenuItems}"
  @forge-select="${handleSelection}"
>
</forge-dropdown>

Properties

PropertyTypeDefaultDescription
itemsDropdownItem[][]Array of menu items to display
labelstring'Options'Text displayed on the trigger button
positionDropdownPosition'bottom-start'Preferred menu position
variantDropdownVariant'default'Visual style of trigger
sizeDropdownSize'medium'Size of the trigger button
disabledbooleanfalseWhether dropdown is disabled
close-on-selectbooleantrueWhether to close menu after selection
multi-selectbooleanfalseAllow multiple item selection
iconstring'โ–ผ'Icon shown on trigger button
placeholderstring''Placeholder text when no selection

Types

interface DropdownItem {
  id: string;
  label: string;
  value?: any;
  icon?: string;
  badge?: string | number;
  disabled?: boolean;
  divider?: boolean;
  group?: string;
  checked?: boolean;
  type?: 'default' | 'checkbox' | 'radio';
  items?: DropdownItem[]; // For nested menus
}
type DropdownPosition = 'bottom-start' | 'bottom-end' | 'top-start' | 'top-end' | 'left' | 'right' | 'auto';
type DropdownVariant = 'default' | 'primary' | 'secondary' | 'minimal';
type DropdownSize = 'small' | 'medium' | 'large';

Events

EventDetailDescription
forge-dropdown{ open: boolean }Fired when dropdown opens/closes
forge-select{ item: DropdownItem, index: number, selected: string[] }Fired when item is selected

Methods

MethodDescription
open()Programmatically open the dropdown
close()Programmatically close the dropdown
toggle()Toggle dropdown open/closed state

Item Types

Default Items

Standard clickable menu items:

{
  id: 'item1',
  label: 'Standard Item',
  value: 'value1',
  icon: '๐Ÿ“„'
}

Checkbox Items

Allow multiple selections:

{
  id: 'check1',
  label: 'Checkbox Item',
  type: 'checkbox',
  checked: true
}

Radio Items

Single selection within a group:

{
  id: 'radio1',
  label: 'Radio Option',
  type: 'radio',
  checked: false
}

Divider Items

Visual separators:

{
  id: 'div1',
  divider: true
}

Grouped Items

Items with group headers:

{
  id: 'grouped1',
  label: 'Group Item',
  group: 'Group Name'
}

Nested Menus

Support for multi-level menu hierarchies:

{
  id: 'parent',
  label: 'Parent Menu',
  items: [
    {
      id: 'child1',
      label: 'Child Item 1',
      value: 'child1'
    },
    {
      id: 'child2',
      label: 'Child Item 2',
      items: [
        {
          id: 'grandchild',
          label: 'Grandchild Item'
        }
      ]
    }
  ]
}

Keyboard Navigation

  • Arrow Keys: Navigate through menu items
  • Enter/Space: Select focused item or open dropdown
  • Escape: Close dropdown menu
  • Home/End: Jump to first/last menu item
  • Tab: Navigate between focusable elements

Smart Positioning

Auto-positioning prevents viewport overflow:

private calculateAutoPosition(): void {
  const spaceBelow = viewportHeight - triggerRect.bottom;
  const spaceAbove = triggerRect.top;
  const spaceRight = viewportWidth - triggerRect.left;
  const spaceLeft = triggerRect.right;

  // Choose best position based on available space
  if (spaceBelow >= menuHeight) {
    position = spaceRight >= menuWidth ? 'bottom-start' : 'bottom-end';
  } else if (spaceAbove >= menuHeight) {
    position = spaceRight >= menuWidth ? 'top-start' : 'top-end';
  }
  // ... additional positioning logic
}

Accessibility Features

  • ARIA Compliance: Full role="combobox" and role="menu" support
  • Screen Readers: Proper announcements for state changes
  • Keyboard Only: Complete keyboard navigation support
  • Focus Management: Appropriate focus trapping and restoration

AI Metadata

Comprehensive AI metadata for intelligent interactions:

{
  purpose: 'Interactive dropdown menu with selectable options',
  criticality: 'medium',
  semanticRole: 'menu',
  interactions: [
    {
      type: 'click',
      description: 'Toggle dropdown menu',
      outcome: 'Opens/closes dropdown'
    },
    {
      type: 'keyboard',
      description: 'Navigate menu with keyboard',
      shortcuts: ['ArrowUp', 'ArrowDown', 'Enter', 'Escape', 'Space']
    }
  ]
}

Performance Monitoring

  • Built-in render time tracking
  • Performance budget enforcement
  • Automatic degradation strategies for complex menus

Styling

CSS Custom Properties

:host {
  --forge-dropdown-trigger-bg: white;
  --forge-dropdown-trigger-hover-bg: #f9fafb;
  --forge-dropdown-menu-bg: white;
  --forge-dropdown-item-hover-bg: #f3f4f6;
  --forge-dropdown-item-selected-bg: #eff6ff;
  --forge-border-color: #e5e7eb;
  --forge-primary-color: #3b82f6;
}

Trigger Variants

  • Default: Standard bordered button appearance
  • Primary: Filled with primary color
  • Secondary: Outlined with primary color
  • Minimal: Borderless, transparent background

Examples

Multi-Select Dropdown

<forge-dropdown
  label="Select Multiple"
  .multiSelect="${true}"
  .closeOnSelect="${false}"
  .items="${[
    { id: '1', label: 'Option 1', type: 'checkbox' },
    { id: '2', label: 'Option 2', type: 'checkbox' },
    { id: '3', label: 'Option 3', type: 'checkbox' }
  ]}"
>
</forge-dropdown>

Grouped Menu

<forge-dropdown
  label="Grouped Options"
  .items="${[
    { id: '1', label: 'File Action', group: 'File', icon: '๐Ÿ“„' },
    { id: '2', label: 'Edit Action', group: 'Edit', icon: 'โœ๏ธ' },
    { id: '3', label: 'View Action', group: 'View', icon: '๐Ÿ‘๏ธ' }
  ]}"
>
</forge-dropdown>

Nested Menu

<forge-dropdown
  label="Nested Menu"
  .items="${[
    { id: '1', label: 'Simple Item' },
    {
      id: '2',
      label: 'Submenu',
      items: [
        { id: '2a', label: 'Sub Item 1' },
        { id: '2b', label: 'Sub Item 2' }
      ]
    }
  ]}"
>
</forge-dropdown>

Testing

Comprehensive test coverage includes:

  • Menu positioning and viewport awareness
  • Keyboard navigation patterns
  • Multi-select functionality
  • Nested menu interactions
  • Accessibility compliance
  • Performance monitoring
// Example test
it('should support keyboard navigation', async () => {
  const dropdown = await fixture<ForgeDropdown>(html`
    <forge-dropdown .items="${testItems}"></forge-dropdown>
  `);
  
  dropdown.open();
  
  // Test arrow key navigation
  await sendKeys({ press: 'ArrowDown' });
  expect(dropdown.focusedIndex).to.equal(0);
});

Best Practices

  1. Item Organization: Use groups for large menus
  2. Keyboard Support: Always test keyboard navigation
  3. Mobile Considerations: Ensure touch-friendly interactions
  4. Performance: Limit deeply nested menus for better performance
  5. Accessibility: Provide meaningful labels and ARIA attributes

Browser Support

  • Modern browsers with Web Components support
  • Graceful keyboard fallbacks
  • Touch-optimized interactions for mobile devices