Comprehensive reusable dropdown components for React with TypeScript and mobile-first design
npm install @asafarim/react-dropdownsProduction-ready dropdown components for React with full TypeScript support, accessibility, and mobile optimization. Built on ASafariM design tokens.
---
- ๐ฏ Comprehensive โ Multiple components for different use cases (simple dropdowns, custom triggers, advanced menus)
- โฟ Fully Accessible โ WCAG 2.1 compliant with keyboard navigation, screen reader support, and ARIA attributes
- ๐ฑ Mobile-First โ Touch-friendly, responsive design with automatic viewport adjustment
- ๐จ Design Token Integration โ Seamless integration with ASafariM design tokens and dark mode support
- ๐ง TypeScript โ Full type safety with IntelliSense and zero runtime overhead
- โก Performant โ Lightweight (~5KB gzipped) with minimal dependencies
- ๐ช Flexible โ 12 placement options, 3 sizes, multiple button variants, and extensive customization
---
``bash`
pnpm add @asafarim/react-dropdowns
Or with your preferred package manager:
`bash`
npm install @asafarim/react-dropdowns
or
yarn add @asafarim/react-dropdowns
Then import the styles in your app (in index.tsx or main.tsx):
`tsx`
import '@asafarim/react-dropdowns/dist/dropdown.css';
---
The simplest way to get started with a basic dropdown menu:
`tsx
import { Dropdown } from '@asafarim/react-dropdowns';
import '@asafarim/react-dropdowns/dist/dropdown.css';
export function App() {
return (
{ id: 'edit', label: 'Edit', onClick: () => console.log('Edit') },
{ id: 'delete', label: 'Delete', danger: true, onClick: () => console.log('Delete') }
]}
placement="bottom-start"
>
Actions
);
}
`
That's it! The dropdown handles state, positioning, keyboard navigation, and accessibility automatically.
---
The main component that combines trigger and menu functionality. Use this for most cases.
Features:
- Automatic state management
- Built-in click-outside detection
- Keyboard navigation (arrow keys, Enter, Escape)
- Automatic menu positioning
- Optional controlled state
Basic Usage:
`tsx`
{
id: 'edit',
label: 'Edit',
icon:
onClick: () => handleEdit()
},
{
id: 'delete',
label: 'Delete',
icon:
danger: true,
onClick: () => handleDelete()
}
]}
placement="bottom-start"
size="md"
>
Actions
With Controlled State:
`tsx
const [isOpen, setIsOpen] = useState(false);
isOpen={isOpen}
onToggle={setIsOpen}
placement="bottom-start"
>
Menu
`
Individual menu item component. Used inside Dropdown or DropdownMenu.
`tsx`
icon={
onClick={() => handleEdit()}
disabled={false}
danger={false}
/>
Low-level menu component for advanced custom implementations. Use with useDropdown hook for full control.
When to use:
- Custom trigger designs (cards, images, etc.)
- Complex menu layouts
- Integration with other positioning libraries
Example:
`tsx
import { createPortal } from 'react-dom';
import { DropdownMenu, DropdownItem, useDropdown, useClickOutside } from '@asafarim/react-dropdowns';
function CustomDropdown() {
const { isOpen, position, toggle, triggerRef, menuRef, close } = useDropdown();
const containerRef = useRef(null);
useClickOutside({
ref: containerRef,
handler: close,
enabled: isOpen,
excludeRefs: [menuRef]
});
return (
{isOpen && createPortal(
document.body
)}
---
๐๏ธ Props Reference
$3
| Prop | Type | Default | Description |
|------|------|---------|-------------|
|
children | ReactNode | โ | Trigger element content |
| items | DropdownItemData[] | [] | Menu items to display |
| isOpen | boolean | โ | (Optional) Controlled open state |
| onToggle | (isOpen: boolean) => void | โ | (Optional) State change callback |
| placement | DropdownPlacement | 'bottom-start' | Menu position relative to trigger |
| size | 'sm' \| 'md' \| 'lg' | 'md' | Menu size |
| variant | ButtonVariant | 'primary' | Trigger button style |
| disabled | boolean | false | Disable the dropdown |
| closeOnSelect | boolean | true | Auto-close menu on item click |
| showChevron | boolean | true | Show chevron icon on trigger |
| className | string | โ | Custom CSS class for wrapper |
| data-testid | string | โ | Test ID for testing |$3
| Prop | Type | Default | Description |
|------|------|---------|-------------|
|
id | string | โ | Unique identifier |
| label | string | โ | Item display text |
| icon | ReactNode | โ | Icon to display before label |
| onClick | (event: MouseEvent) => void | โ | Click handler |
| disabled | boolean | false | Disable the item |
| danger | boolean | false | Red danger styling |
| divider | boolean | false | Render as visual separator |
| value | string | โ | Optional data value |$3
| Prop | Type | Default | Description |
|------|------|---------|-------------|
|
children | ReactNode | โ | Menu content |
| isOpen | boolean | โ | Show/hide menu |
| position | DropdownPosition | โ | Absolute position (from useDropdown) |
| size | 'sm' \| 'md' \| 'lg' | 'md' | Menu size |
| className | string | โ | Custom CSS class |
| ref | RefObject | โ | Menu element reference |---
๐จ Customization
$3
Position the menu relative to the trigger:
`
Top: top | top-start | top-end
Bottom: bottom | bottom-start | bottom-end
Left: left | left-start | left-end
Right: right | right-start | right-end
``tsx
Menu
`$3
`tsx
Compact
Default
Large
`$3
Style the trigger button:
`tsx
Primary
Secondary
Ghost
Outline
Danger
`$3
Override default styles using CSS classes:
`css
/ Menu container /
.asm-dropdown-menu {
background: var(--asm-color-surface);
border: 1px solid var(--asm-color-border);
}/ Menu item /
.asm-dropdown-item {
padding: var(--asm-space-3);
}
/ Danger item /
.asm-dropdown-item--danger {
color: var(--asm-color-danger);
}
/ Disabled item /
.asm-dropdown-item:disabled {
opacity: 0.5;
}
`---
๐ช Hooks
$3
Build custom dropdowns with full control over positioning and state.
Returns:
`tsx
const {
isOpen, // boolean - Menu visibility state
position, // DropdownPosition - Calculated position
triggerRef, // RefObject - Attach to trigger element
menuRef, // RefObject - Attach to menu element
toggle, // () => void - Toggle open/closed
open, // () => void - Open menu
close, // () => void - Close menu
handleItemClick // () => void - Handle item selection
} = useDropdown({
placement: 'bottom-start',
offset: 8,
closeOnSelect: true
});
`Example:
`tsx
function CustomDropdown() {
const { isOpen, position, toggle, triggerRef, menuRef } = useDropdown(); return (
<>
{isOpen && (
{/ Menu items /}
)}
>
);
}
`$3
Detect clicks outside an element to close menus.
`tsx
useClickOutside({
ref: containerRef, // Element to monitor
handler: () => setIsOpen(false), // Callback on outside click
enabled: isOpen, // Enable/disable detection
excludeRefs: [menuRef] // Refs to exclude from detection
});
`$3
Add keyboard navigation to custom dropdowns.
`tsx
useKeyboardNavigation({
isOpen, // boolean
menuRef, // RefObject to menu
onClose: () => setIsOpen(false),
onSelect: (index) => selectItem(index)
});
`---
โฟ Accessibility
Built with WCAG 2.1 AA compliance in mind:
- Keyboard Navigation โ Full support for arrow keys, Enter, Escape, Home, End
- Screen Readers โ Proper ARIA roles, labels, and live regions
- Focus Management โ Automatic focus handling and restoration
- High Contrast โ Works with high contrast mode
- Reduced Motion โ Respects
prefers-reduced-motion setting$3
| Key | Action |
|-----|--------|
|
Space / Enter | Toggle menu or select item |
| Arrow Down | Next item / Open menu |
| Arrow Up | Previous item |
| Home | First item |
| End | Last item |
| Escape | Close menu |
| Tab | Close menu and move focus |---
๐ก Real-World Examples
$3
`tsx
items={[
{ id: 'new', label: 'New', icon: },
{ id: 'open', label: 'Open', icon: },
{ divider: true },
{ id: 'save', label: 'Save', icon: },
{ id: 'export', label: 'Export', icon: },
{ divider: true },
{ id: 'exit', label: 'Exit', danger: true, icon: }
]}
placement="bottom-start"
>
File
`$3
`tsx
const [user, setUser] = useState({ name: 'John Doe', avatar: '...' }); items={[
{ id: 'profile', label: 'Profile', icon: },
{ id: 'settings', label: 'Settings', icon: },
{ divider: true },
{ id: 'logout', label: 'Logout', danger: true, icon: }
]}
placement="bottom-end"
>

`$3
`tsx
const [filter, setFilter] = useState('all'); items={[
{
id: 'all',
label: 'All Items',
icon: filter === 'all' ? : undefined,
onClick: () => setFilter('all')
},
{
id: 'active',
label: 'Active Only',
icon: filter === 'active' ? : undefined,
onClick: () => setFilter('active')
},
{
id: 'archived',
label: 'Archived',
icon: filter === 'archived' ? : undefined,
onClick: () => setFilter('archived')
}
]}
placement="bottom-start"
>
{filter}
`$3
See the demo app for a complete example using
useDropdown with custom card trigger styling.---
๐งช Testing
All components are fully testable with standard React testing libraries:
`tsx
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';test('opens dropdown on click', async () => {
render(
Trigger
);
const trigger = screen.getByText('Trigger');
await userEvent.click(trigger);
expect(screen.getByText('Test')).toBeInTheDocument();
});
`---
๐ Browser Support
| Browser | Version |
|---------|---------|
| Chrome | 88+ |
| Firefox | 78+ |
| Safari | 14+ |
| Edge | 88+ |
---
๐ค Contributing
Contributions are welcome! Please follow these steps:
1. Fork the repository
2. Create a feature branch:
git checkout -b feature/your-feature
3. Commit changes: git commit -m 'Add your feature'
4. Push to branch: git push origin feature/your-feature
5. Open a Pull Request---
๐ License
MIT ยฉ ASafariM
---
๐ Resources
- Live Demo
- GitHub Repository
- npm Package
- ASafariM Design Tokens
---
๐ Changelog
$3
- Added an advanced
useDropdown demo section with custom trigger, portal rendering, and click-outside handling
- Documented low-level hook usage with full examples and testing guidance
- Rewrote README for clearer onboarding (installation, components, customization)
- Improved demo styles and behavior (auto-close on outside click, refined trigger states)$3
- Added automatic chevron icon to dropdown triggers
- Added
showChevron prop to control chevron visibility
- Fixed Vite base path configuration for GitHub Pages deployment
- Improved demo app layout with grid-based examples
- Added support for multiple button variants in trigger
- Added advanced custom dropdown example with useDropdown` hook- Enhanced demo app with variant examples
- Improved styling and layout
- Better mobile responsiveness
- Initial release
- Comprehensive dropdown components
- Mobile-first responsive design
- Full accessibility support
- TypeScript support
- ASafariM design token integration