Dropdown
freeDropdown keeps actions and option lists available from a compact trigger.
@floating-ui/react
Preview
Dark
JB
Jordan Blake
jordan@example.com
import Avatar from '@/components/ui/Avatar';
import Dropdown from '@/components/ui/Dropdown';
import Switcher from '@/components/ui/Switcher';
import {
PiCaretUpDown,
PiGear,
PiMoon,
PiPulse,
PiSignOut,
PiUserCircle,
} from 'react-icons/pi'
export default function UsageDemo() {
return (
<div className="flex justify-center">
<Dropdown
menuClass="min-w-56"
renderTitle={
<div className="flex cursor-pointer items-center gap-2 rounded-control p-2 transition-colors hover:bg-accent">
<Avatar size="sm" className="bg-palette-purple text-white">
JB
</Avatar>
<div className="text-left">
<div className="text-sm font-semibold text-foreground">
Jordan Blake
</div>
<div className="text-xs text-muted-foreground">
jordan@example.com
</div>
</div>
<PiCaretUpDown className="text-base text-muted-foreground" />
</div>
}
>
<Dropdown.Item eventKey="profile" className="flex items-center gap-2">
<PiUserCircle className="text-base text-muted-foreground" />
Profile
</Dropdown.Item>
<Dropdown.Item eventKey="settings" className="flex items-center gap-2">
<PiGear className="text-base text-muted-foreground" />
Account Settings
</Dropdown.Item>
<Dropdown.Item eventKey="activity" className="flex items-center gap-2">
<PiPulse className="text-base text-muted-foreground" />
Activity Log
</Dropdown.Item>
<Dropdown.Item variant="divider" />
<Dropdown.Item
variant="custom"
closeOnClick={false}
className="flex w-full cursor-pointer items-center justify-between gap-2 rounded-control-sm px-3 py-2 font-medium text-popover-foreground hover:bg-accent hover:text-accent-foreground"
>
<span className="flex items-center gap-2">
<PiMoon className="text-base text-muted-foreground" />
Dark Mode
</span>
<Switcher aria-label="Dark mode" defaultChecked />
</Dropdown.Item>
<Dropdown.Item variant="divider" />
<Dropdown.Item
variant="custom"
eventKey="sign-out"
className="flex cursor-pointer items-center gap-2 rounded-control-sm px-3 py-2 font-medium text-destructive"
>
<PiSignOut className="text-base" />
Sign Out
</Dropdown.Item>
</Dropdown>
</div>
);
}Installation
Add this component with the NateUI CLI.
npx nateui@latest add DropdownExamples
Basic
Preview
Dark
import Dropdown from '@/components/ui/Dropdown';
export default function BasicDemo() {
return (
<div className="flex justify-center">
<Dropdown title="Options">
<Dropdown.Item eventKey="edit">Edit</Dropdown.Item>
<Dropdown.Item eventKey="duplicate">Duplicate</Dropdown.Item>
<Dropdown.Item eventKey="archive">Archive</Dropdown.Item>
</Dropdown>
</div>
);
}Custom Toggle
Preview
Dark
import { PiArchive, PiCopy, PiPencilSimple } from 'react-icons/pi'
import Button from '@/components/ui/Button';
import Dropdown from '@/components/ui/Dropdown';
export default function CustomToggleDemo() {
return (
<div className="flex justify-center">
<Dropdown renderTitle={<Button variant="solid">Actions</Button>}>
<Dropdown.Item eventKey="edit" className="flex items-center gap-2">
<PiPencilSimple className="text-base text-muted-foreground" />
Edit
</Dropdown.Item>
<Dropdown.Item eventKey="duplicate" className="flex items-center gap-2">
<PiCopy className="text-base text-muted-foreground" />
Duplicate
</Dropdown.Item>
<Dropdown.Item eventKey="archive" className="flex items-center gap-2">
<PiArchive className="text-base text-muted-foreground" />
Archive
</Dropdown.Item>
</Dropdown>
</div>
);
}Trigger
Preview
Dark
import { PiArchive, PiCopy, PiPencilSimple } from 'react-icons/pi'
import Dropdown from '@/components/ui/Dropdown';
export default function TriggerDemo() {
return (
<div className="flex flex-wrap justify-center gap-2">
<Dropdown title="Click">
<Dropdown.Item eventKey="edit" className="flex items-center gap-2">
<PiPencilSimple className="text-base text-muted-foreground" />
Edit
</Dropdown.Item>
<Dropdown.Item eventKey="duplicate" className="flex items-center gap-2">
<PiCopy className="text-base text-muted-foreground" />
Duplicate
</Dropdown.Item>
<Dropdown.Item eventKey="archive" className="flex items-center gap-2">
<PiArchive className="text-base text-muted-foreground" />
Archive
</Dropdown.Item>
</Dropdown>
<Dropdown title="Hover" trigger="hover">
<Dropdown.Item eventKey="edit" className="flex items-center gap-2">
<PiPencilSimple className="text-base text-muted-foreground" />
Edit
</Dropdown.Item>
<Dropdown.Item eventKey="duplicate" className="flex items-center gap-2">
<PiCopy className="text-base text-muted-foreground" />
Duplicate
</Dropdown.Item>
<Dropdown.Item eventKey="archive" className="flex items-center gap-2">
<PiArchive className="text-base text-muted-foreground" />
Archive
</Dropdown.Item>
</Dropdown>
<Dropdown title="Right Click" trigger="rightClick">
<Dropdown.Item eventKey="edit" className="flex items-center gap-2">
<PiPencilSimple className="text-base text-muted-foreground" />
Edit
</Dropdown.Item>
<Dropdown.Item eventKey="duplicate" className="flex items-center gap-2">
<PiCopy className="text-base text-muted-foreground" />
Duplicate
</Dropdown.Item>
<Dropdown.Item eventKey="archive" className="flex items-center gap-2">
<PiArchive className="text-base text-muted-foreground" />
Archive
</Dropdown.Item>
</Dropdown>
</div>
);
}Submenu
Preview
Dark
import {
PiArchive,
PiEnvelopeSimple,
PiFolderOpen,
PiFolderPlus,
PiPencilSimple,
PiTrash,
PiTray,
} from 'react-icons/pi'
import Dropdown from '@/components/ui/Dropdown';
export default function SubmenuDemo() {
return (
<div className="flex justify-center">
<Dropdown title="Options">
<Dropdown.Item eventKey="edit" className="flex items-center gap-2">
<PiPencilSimple className="text-base text-muted-foreground" />
Edit
</Dropdown.Item>
<Dropdown.Menu
renderTitle={
<span className="flex items-center gap-2">
<PiFolderPlus className="text-base text-muted-foreground" />
Move to
</span>
}
>
<Dropdown.Item eventKey="move-inbox" className="flex items-center gap-2">
<PiTray className="text-base text-muted-foreground" />
Inbox
</Dropdown.Item>
<Dropdown.Item eventKey="move-archive" className="flex items-center gap-2">
<PiArchive className="text-base text-muted-foreground" />
Archive
</Dropdown.Item>
<Dropdown.Menu
renderTitle={
<span className="flex items-center gap-2">
<PiFolderOpen className="text-base text-muted-foreground" />
More
</span>
}
>
<Dropdown.Item eventKey="move-trash" className="flex items-center gap-2">
<PiTrash className="text-base text-muted-foreground" />
Trash
</Dropdown.Item>
<Dropdown.Item eventKey="move-spam" className="flex items-center gap-2">
<PiEnvelopeSimple className="text-base text-muted-foreground" />
Spam
</Dropdown.Item>
</Dropdown.Menu>
</Dropdown.Menu>
<Dropdown.Item eventKey="delete" className="flex items-center gap-2">
<PiTrash className="text-base text-muted-foreground" />
Delete
</Dropdown.Item>
</Dropdown>
</div>
);
}Placement
Preview
Dark
import { PiArchive, PiCopy, PiPencilSimple } from 'react-icons/pi'
import Dropdown from '@/components/ui/Dropdown';
const DropdownItems = () => (
<>
<Dropdown.Item eventKey="edit" className="flex items-center gap-2">
<PiPencilSimple className="text-base text-muted-foreground" />
Edit
</Dropdown.Item>
<Dropdown.Item eventKey="duplicate" className="flex items-center gap-2">
<PiCopy className="text-base text-muted-foreground" />
Duplicate
</Dropdown.Item>
<Dropdown.Item eventKey="archive" className="flex items-center gap-2">
<PiArchive className="text-base text-muted-foreground" />
Archive
</Dropdown.Item>
</>
);
export default function PlacementDemo() {
return (
<div className="grid grid-cols-2 gap-4 sm:grid-cols-3">
<Dropdown placement="top-start" title="Top start">
<DropdownItems />
</Dropdown>
<Dropdown placement="top" title="Top">
<DropdownItems />
</Dropdown>
<Dropdown placement="top-end" title="Top end">
<DropdownItems />
</Dropdown>
<Dropdown placement="bottom-start" title="Bottom start">
<DropdownItems />
</Dropdown>
<Dropdown placement="bottom" title="Bottom">
<DropdownItems />
</Dropdown>
<Dropdown placement="bottom-end" title="Bottom end">
<DropdownItems />
</Dropdown>
<Dropdown placement="right-start" title="Right start">
<DropdownItems />
</Dropdown>
<Dropdown placement="right" title="Right">
<DropdownItems />
</Dropdown>
<Dropdown placement="right-end" title="Right end">
<DropdownItems />
</Dropdown>
<Dropdown placement="left-start" title="Left start">
<DropdownItems />
</Dropdown>
<Dropdown placement="left" title="Left">
<DropdownItems />
</Dropdown>
<Dropdown placement="left-end" title="Left end">
<DropdownItems />
</Dropdown>
</div>
);
}Active Item
Preview
Dark
import { PiSortAscending, PiSortDescending, PiTextAa } from 'react-icons/pi'
import Dropdown from '@/components/ui/Dropdown';
export default function ActiveItemDemo() {
return (
<div className="flex justify-center">
<Dropdown title="Sort by" activeKey="newest">
<Dropdown.Item eventKey="newest" className="flex items-center gap-2">
<PiSortDescending className="text-base text-muted-foreground" />
Newest first
</Dropdown.Item>
<Dropdown.Item eventKey="oldest" className="flex items-center gap-2">
<PiSortAscending className="text-base text-muted-foreground" />
Oldest first
</Dropdown.Item>
<Dropdown.Item eventKey="az" className="flex items-center gap-2">
<PiTextAa className="text-base text-muted-foreground" />
A–Z
</Dropdown.Item>
</Dropdown>
</div>
);
}Disabled
Preview
Dark
import { PiArchive, PiCopy, PiPencilSimple } from 'react-icons/pi'
import Dropdown from '@/components/ui/Dropdown';
export default function DisabledDemo() {
return (
<div className="flex flex-wrap justify-center gap-2">
<Dropdown disabled title="Options">
<Dropdown.Item eventKey="edit" className="flex items-center gap-2">
<PiPencilSimple className="text-base text-muted-foreground" />
Edit
</Dropdown.Item>
<Dropdown.Item eventKey="duplicate" className="flex items-center gap-2">
<PiCopy className="text-base text-muted-foreground" />
Duplicate
</Dropdown.Item>
<Dropdown.Item eventKey="archive" className="flex items-center gap-2">
<PiArchive className="text-base text-muted-foreground" />
Archive
</Dropdown.Item>
</Dropdown>
<Dropdown title="Options">
<Dropdown.Item eventKey="edit" className="flex items-center gap-2">
<PiPencilSimple className="text-base text-muted-foreground" />
Edit
</Dropdown.Item>
<Dropdown.Item
eventKey="duplicate"
disabled
className="flex items-center gap-2"
>
<PiCopy className="text-base text-muted-foreground" />
Duplicate
</Dropdown.Item>
<Dropdown.Item eventKey="archive" className="flex items-center gap-2">
<PiArchive className="text-base text-muted-foreground" />
Archive
</Dropdown.Item>
</Dropdown>
</div>
);
}Item Variants
Preview
Dark
import { ReactNode } from 'react';
import {
PiBuilding,
PiCreditCard,
PiGear,
PiHeadphones,
PiSignOut,
PiUser,
PiUsers,
} from 'react-icons/pi'
import Dropdown from '@/components/ui/Dropdown';
import Kbd from '@/components/ui/Kbd';
const ItemContent = ({
icon,
suffix,
children,
}: {
icon: ReactNode;
suffix?: string;
children: ReactNode;
}) => (
<div className="flex w-full items-center justify-between gap-2">
<div className="flex items-center gap-2">
{icon}
{children}
</div>
{suffix && <Kbd>{suffix}</Kbd>}
</div>
);
export default function ItemVariantsDemo() {
return (
<div className="flex justify-center">
<Dropdown title="Account">
<Dropdown.Item
variant="header"
className="px-2 py-1.5 text-xs font-semibold uppercase tracking-wide text-muted-foreground"
>
My Account
</Dropdown.Item>
<Dropdown.Item variant="divider" />
<Dropdown.Item eventKey="profile">
<ItemContent
icon={<PiUser className="text-base text-muted-foreground" />}
suffix="⇧⌘P"
>
Profile
</ItemContent>
</Dropdown.Item>
<Dropdown.Item eventKey="settings">
<ItemContent
icon={<PiGear className="text-base text-muted-foreground" />}
suffix="⌘,"
>
Settings
</ItemContent>
</Dropdown.Item>
<Dropdown.Item eventKey="billing">
<ItemContent
icon={<PiCreditCard className="text-base text-muted-foreground" />}
suffix="⌘B"
>
Billing
</ItemContent>
</Dropdown.Item>
<Dropdown.Item eventKey="support">
<ItemContent
icon={<PiHeadphones className="text-base text-muted-foreground" />}
suffix="⌘/"
>
Support
</ItemContent>
</Dropdown.Item>
<Dropdown.Item variant="divider" />
<Dropdown.Item eventKey="team">
<ItemContent icon={<PiUsers className="text-base text-muted-foreground" />}>
Team
</ItemContent>
</Dropdown.Item>
<Dropdown.Item eventKey="organization">
<ItemContent
icon={<PiBuilding className="text-base text-muted-foreground" />}
>
Organization
</ItemContent>
</Dropdown.Item>
<Dropdown.Item variant="divider" />
<Dropdown.Item
variant="custom"
eventKey="logout"
className="flex cursor-pointer items-center gap-2 rounded-control-sm px-3 py-2 font-medium text-destructive hover:bg-destructive-soft"
>
<PiSignOut className="text-base" />
Logout
</Dropdown.Item>
</Dropdown>
</div>
);
}Context Menu
Preview
Dark
Right click here
import { PiCopy, PiPencilSimple, PiTrash } from 'react-icons/pi'
import Dropdown from '@/components/ui/Dropdown';
export default function ContextMenuDemo() {
return (
<Dropdown.ContextMenu
areaClass="flex h-36 min-w-52 items-center justify-center rounded-card border border-dashed border-border bg-muted text-muted-foreground"
areaContent={<span className="font-medium">Right click here</span>}
>
<Dropdown.Item eventKey="edit" className="flex items-center gap-2">
<PiPencilSimple className="text-base text-muted-foreground" />
Edit
</Dropdown.Item>
<Dropdown.Item eventKey="duplicate" className="flex items-center gap-2">
<PiCopy className="text-base text-muted-foreground" />
Duplicate
</Dropdown.Item>
<Dropdown.Item eventKey="delete" className="flex items-center gap-2">
<PiTrash className="text-base text-muted-foreground" />
Delete
</Dropdown.Item>
</Dropdown.ContextMenu>
);
}Link
Preview
Dark
import { PiArrowRight } from 'react-icons/pi'
import Dropdown from '@/components/ui/Dropdown';
export default function LinkDemo() {
return (
<div className="flex justify-center">
<Dropdown title="Components">
<Dropdown.Item
asElement="a"
href="/components/ui/checkbox"
className="flex items-center gap-2"
>
<PiArrowRight className="text-base text-muted-foreground" />
Checkbox
</Dropdown.Item>
<Dropdown.Item
asElement="a"
href="/components/ui/button"
className="flex items-center gap-2"
>
<PiArrowRight className="text-base text-muted-foreground" />
Button
</Dropdown.Item>
<Dropdown.Item
asElement="a"
href="/components/ui/alert"
className="flex items-center gap-2"
>
<PiArrowRight className="text-base text-muted-foreground" />
Alert
</Dropdown.Item>
<Dropdown.Item
asElement="a"
href="/components/ui/dialog"
className="flex items-center gap-2"
>
<PiArrowRight className="text-base text-muted-foreground" />
Dialog
</Dropdown.Item>
</Dropdown>
</div>
);
}API
Dropdown is a compound component: the root trigger, Dropdown.Item, Dropdown.Menu for submenus, and Dropdown.ContextMenu for right-click menus.
Dropdown
| Prop | Description | Type | Default |
|---|---|---|---|
activeKey | Matches a Dropdown.Item's eventKey to mark it active. | string | - |
disabled | Prevents the dropdown from opening. | boolean | - |
menuClass | Class names applied to the menu surface. | string | - |
onOpen | Called when the open state changes. | DropdownOpenChangeHandler | - |
onSelect | Called when a Dropdown.Item inside is selected. | DropdownSelectHandler | - |
placement | Side and alignment the menu opens from, relative to the trigger. | DropdownPlacement | 'bottom-start' |
renderTitle | Custom trigger content, replacing the default toggle. | ReactNode | - |
title | Label rendered in the default toggle. | string | ReactNode | - |
toggleClassName | Class names applied to the trigger element. | string | - |
trigger | How the menu opens: on click, hover, or right-click. | 'click' | 'hover' | 'rightClick' | 'click' |
...nativeProps | Native props applied to the trigger element. | ComponentProps<'div'> | - |
Dropdown.Item
| Prop | Description | Type | Default |
|---|---|---|---|
active | Marks the item as active, independent of activeKey matching. | boolean | - |
asElement | Element or component the item renders as. | ElementType | 'li' |
closeOnClick | Closes the dropdown after this item is selected. | boolean | true |
disabled | Prevents interaction with this item. | boolean | - |
eventKey | Value passed to onSelect and matched against activeKey. | string | - |
menuItemHeight | Fixed height of the item, in pixels. | number | 36 |
onClick | Called when the item is clicked. | (e: SyntheticEvent) => void | - |
onFocus | Called when the item receives focus. | (e: SyntheticEvent) => void | - |
onSelect | Called with the item's eventKey when selected. | DropdownSelectHandler | - |
variant | Visual role of the item. | DropdownItemVariant | 'default' |
...nativeProps | Native props applied to the rendered element. | ComponentProps<'li'> | - |
Dropdown.Menu
Renders a nested submenu. Used as a child of Dropdown or another Dropdown.Menu.
| Prop | Description | Type | Default |
|---|---|---|---|
eventKey | Value matched against the parent's activeKey to highlight this submenu. | string | - |
menuClass | Class names applied to the submenu surface. | string | - |
onOpen | Called when the submenu's open state changes. | DropdownOpenChangeHandler | - |
placement | Side the submenu opens from, relative to its parent item. | DropdownPlacement | 'right-start' |
renderTitle | Custom content replacing the default submenu trigger row. | ReactNode | - |
title | Label rendered as the submenu's trigger row. | string | ReactNode | - |
Dropdown.ContextMenu
Renders its areaContent inside a region that opens the menu on right-click, instead of a click-to-open toggle.
| Prop | Description | Type | Default |
|---|---|---|---|
activeKey | Matches a Dropdown.Item's eventKey to mark it active. | string | - |
areaClass | Class names applied to the right-click area. | string | - |
areaContent | Content rendered inside the right-click area. Required. | ReactNode | - |
areaRef | Ref forwarded to the right-click area element. | Ref<HTMLDivElement> | - |
children | Dropdown.Item / Dropdown.Menu elements rendered as the menu content. | ReactNode | - |
disabled | Prevents the menu from opening. | boolean | - |
menuClass | Class names applied to the menu surface. | string | - |
onOpen | Called when the open state changes — right-click (or openAt/handleDropdownOpen) to open, and Escape, outside click, or handleDropdownClose to close. | DropdownOpenChangeHandler | - |
placement | Side and alignment the menu opens from, relative to the cursor. | DropdownPlacement | 'bottom-start' |
ref | Imperative handle for opening and closing the menu programmatically. | Ref<DropdownContextMenuRef> | - |
Types
type DropdownPlacement =
| 'top'
| 'top-start'
| 'top-end'
| 'right'
| 'right-start'
| 'right-end'
| 'bottom'
| 'bottom-start'
| 'bottom-end'
| 'left'
| 'left-start'
| 'left-end';
type DropdownItemVariant = 'default' | 'header' | 'divider' | 'custom';
type DropdownOpenChangeHandler = (open: boolean) => void;
type DropdownSelectHandler = (eventKey: string, e: SyntheticEvent) => void;
type DropdownContextMenuRef = {
openAt: (x: number, y: number) => void;
handleDropdownOpen: () => void;
handleDropdownClose: () => void;
};