Dropdown
An accessible dropdown menu component for displaying contextual actions or navigation options. Supports full keyboard navigation, focus management, and portal-based rendering.
Usage
Basic usage
import {
Dropdown,
DropdownTrigger,
DropdownMenu,
DropdownItem,
} from '@nofinite/nui';
<Dropdown>
<DropdownTrigger>Menu</DropdownTrigger>
<DropdownMenu>
<DropdownItem>Profile</DropdownItem>
<DropdownItem>Settings</DropdownItem>
<DropdownItem>Logout</DropdownItem>
</DropdownMenu>
</Dropdown>;
With interaction handlers
<Dropdown>
<DropdownTrigger>Actions</DropdownTrigger>
<DropdownMenu>
<DropdownItem onSelect={() => console.log('Edit')}>Edit</DropdownItem>
<DropdownItem onSelect={() => console.log('Delete')}>Delete</DropdownItem>
</DropdownMenu>
</Dropdown>
Variants
This dropdown does not expose explicit visual variants via props.
Styling approach:
- Use
classNameonDropdownTrigger,DropdownMenu, orDropdownItem - Theme is controlled via CSS variables
<DropdownTrigger className="my-trigger" />
<DropdownMenu className="my-menu" />
<DropdownItem className="my-item" />
Sizes
No built-in size prop is provided.
Sizing is controlled via CSS:
- Padding
- Font size
- Min-width
.ui-dropdown-menu {
min-width: 200px;
}
States
Open / Closed
-
Controlled internally via trigger click
-
Automatically closes on:
- Click outside
Escapekey- Item selection
Focus States
- Focus moves to first item when menu opens
- Focus is restored to trigger when menu closes
Native Props / Composition
Trigger
DropdownTrigger renders a native <button> and supports standard button attributes.
<DropdownTrigger aria-label="Open menu" onClick={customHandler}>
Menu
</DropdownTrigger>
Menu Item
DropdownItem renders a focusable element with role="menuitem".
<DropdownItem data-testid="logout-item">Logout</DropdownItem>
Props
<Dropdown />
| Prop | Type | Default | Description |
|---|---|---|---|
| children | ReactNode | — | Dropdown trigger and menu components |
<DropdownTrigger />
| Prop | Type | Default | Description |
|---|---|---|---|
| children | ReactNode | — | Trigger content |
| className | string | "" | Custom CSS class names |
<DropdownMenu />
| Prop | Type | Default | Description |
|---|---|---|---|
| children | ReactNode | — | Dropdown items |
| className | string | "" | Custom CSS class names |
<DropdownItem />
| Prop | Type | Default | Description |
|---|---|---|---|
| children | ReactNode | — | Item content |
| onSelect | () => void | — | Called when item is selected |
| className | string | "" | Custom CSS class names |
Behavior Notes
- Uses roving tabindex for arrow-key navigation
- Arrow keys move focus between items
Enter/Spacetriggers item selectionEscapecloses the menu- Clicking an item automatically closes the menu
- Menu is rendered in a portal to avoid overflow issues
<DropdownItem onSelect={handleSelect} />
Accessibility
-
Trigger:
- Renders as
<button> - Uses
aria-haspopup="menu" - Uses
aria-expanded
- Renders as
-
Menu:
role="menu"- Keyboard navigable (↑ ↓ Enter Esc)
-
Menu Item:
role="menuitem"- Programmatically focusable
- WCAG-compliant focus styles
Recommended for icon-only triggers:
<DropdownTrigger aria-label="Open options">
<IconMore />
</DropdownTrigger>
Layout
- Dropdown root is
inline-block - Menu is absolutely positioned via portal
- Does not affect document flow
<div style={{ display: 'flex', justifyContent: 'flex-end' }}>
<Dropdown>
<DropdownTrigger>Menu</DropdownTrigger>
<DropdownMenu>...</DropdownMenu>
</Dropdown>
</div>
Best Practices
Do
- Use for short, contextual actions
- Keep item labels concise
- Group related actions together
- Use keyboard navigation as primary UX reference
Don’t
- Place complex forms inside menu items
- Use for large navigation menus
- Nest dropdowns inside dropdown items
- Rely on hover-only interactions