Menu
Accessible dropdown menu for actions and navigation
Menu
An accessible, keyboard-navigable dropdown menu for actions and navigation. Built with Floating UI for positioning.
Installation
import { Menu, MenuItem, MenuTrigger } from '@geckoui/geckoui';Basic Usage
<Menu label="Options">
<MenuItem label="Edit" onClick={() => console.log('Edit')} />
<MenuItem label="Duplicate" onClick={() => console.log('Duplicate')} />
<MenuItem label="Delete" onClick={() => console.log('Delete')} />
</Menu>Components
Menu
Root component that manages state, positioning, and renders the default button.
| Prop | Type | Default | Description |
|---|---|---|---|
label | string | "Menu" | Text for the default button |
disabled | boolean | false | Disable the menu |
placement | Placement | "bottom-start" | Dropdown position |
className | string | - | Wrapper class |
menuClassName | string | - | Dropdown panel class |
buttonClassName | string | - | Default button class |
MenuItem
Individual action item within the menu. Closes the menu on click.
| Prop | Type | Description |
|---|---|---|
label | string | Item text (rendered if no children) |
onClick | () => void | Click handler |
disabled | boolean | Disable the item |
className | string | Additional class |
children | ReactNode | Function | Custom content or render function |
Render function receives { focused: boolean }.
MenuTrigger
Custom trigger using render props. Replaces the default button.
<Menu>
<MenuTrigger>
{({ open, toggleMenu, disabled }) => (
<button onClick={toggleMenu}>
Actions {open ? '▲' : '▼'}
</button>
)}
</MenuTrigger>
<MenuItem label="Edit" onClick={() => {}} />
</Menu>Examples
With Icons
import { Edit, Copy, Trash } from 'lucide-react';
<Menu>
<MenuTrigger>
{({ toggleMenu }) => (
<Button onClick={toggleMenu}>
Actions <ChevronDown className="size-4" />
</Button>
)}
</MenuTrigger>
<MenuItem label="Edit" onClick={() => console.log('Edit')}>
<div className="flex items-center gap-3">
<Edit className="size-4" />
<span>Edit</span>
</div>
</MenuItem>
<MenuItem label="Delete" onClick={() => console.log('Delete')}>
<div className="flex items-center gap-3">
<Trash className="size-4 text-red-600" />
<span className="text-red-600">Delete</span>
</div>
</MenuItem>
</Menu>Disabled Items
<Menu label="Actions">
<MenuItem label="Available Action" onClick={() => console.log('Available')} />
<MenuItem label="Disabled Action" disabled />
<MenuItem label="Another Action" onClick={() => console.log('Another')} />
</Menu>Custom Button
<Menu>
<MenuTrigger>
{({ toggleMenu }) => (
<Button variant="filled" onClick={toggleMenu}>
User Menu
</Button>
)}
</MenuTrigger>
<MenuItem label="Profile" onClick={() => console.log('Profile')} />
<MenuItem label="Settings" onClick={() => console.log('Settings')} />
<MenuItem label="Logout" onClick={() => console.log('Logout')}>
<span className="text-red-600">Logout</span>
</MenuItem>
</Menu>Placement
<Menu label="Top Start" placement="top-start">
<MenuItem label="Option 1" />
<MenuItem label="Option 2" />
</Menu>
<Menu label="Bottom End" placement="bottom-end">
<MenuItem label="Option 1" />
<MenuItem label="Option 2" />
</Menu>Render Function
Use a render function in children to access focus state:
<MenuItem label="Edit" onClick={handleEdit}>
{({ focused }) => (
<div className={focused ? 'font-bold' : ''}>
{focused ? '→ ' : ''}Edit
</div>
)}
</MenuItem>Keyboard Navigation
- Enter/Space on button: Toggle menu
- Arrow Down/Up: Navigate items
- Enter/Space on item: Trigger action
- Escape/Tab: Close menu
Styling
GeckoUIMenu- WrapperGeckoUIMenu__button- Default buttonGeckoUIMenu__items- Dropdown panelGeckoUIMenu__item- Individual itemdata-focused- Present when focuseddata-disabled- Present when disabled
Accessibility
role="menu"on dropdown panelrole="menuitem"on itemsaria-haspopupandaria-expandedon button- Full keyboard navigation
- Focus management