Components
Dropdown
A dropdown menu component with items, groups, sub-menus, error variants, and a composed shorthand for common patterns.
Installation
bunx @happlyui/cli@latest add dropdown
Dependencies
npm packages
@radix-ui/react-dropdown-menu@remixicon/react
Registry dependencies
These are automatically installed when you add this component.
happly-ui-utilspolymorphic
Usage
import * as Dropdown from "@/components/ui/dropdown"
<Dropdown.Root>
<Dropdown.Trigger asChild>
<button>Open</button>
</Dropdown.Trigger>
<Dropdown.Content>
<Dropdown.Item>Item</Dropdown.Item>
</Dropdown.Content>
</Dropdown.Root>
Examples
Default
Basic dropdown with icon items.
<Dropdown.Root>
<Dropdown.Trigger asChild>
<Button.Root variant="neutral" mode="stroke">
Open Dropdown
</Button.Root>
</Dropdown.Trigger>
<Dropdown.Content align="start">
<Dropdown.Group>
<Dropdown.Item>
<Dropdown.ItemIcon as={RiSettings2Line} />
Settings
</Dropdown.Item>
<Dropdown.Item>
<Dropdown.ItemIcon as={RiAddLine} />
Add Item
</Dropdown.Item>
<Dropdown.Item>
<Dropdown.ItemIcon as={RiLogoutBoxRLine} />
Logout
</Dropdown.Item>
</Dropdown.Group>
</Dropdown.Content>
</Dropdown.Root>
With Groups
Grouped items with labels and a divider between sections.
<Dropdown.Root>
<Dropdown.Trigger asChild>
<Button.Root variant="neutral" mode="stroke">
Open Dropdown
</Button.Root>
</Dropdown.Trigger>
<Dropdown.Content align="start">
<Dropdown.Group>
<Dropdown.Label>Account</Dropdown.Label>
<Dropdown.Item>
<Dropdown.ItemIcon as={RiPulseLine} />
Activity
</Dropdown.Item>
<Dropdown.Item>
<Dropdown.ItemIcon as={RiSettings2Line} />
Settings
</Dropdown.Item>
</Dropdown.Group>
<Divider.Root variant="line-spacing" />
<Dropdown.Group>
<Dropdown.Label>Actions</Dropdown.Label>
<Dropdown.Item>
<Dropdown.ItemIcon as={RiAddLine} />
Add Account
</Dropdown.Item>
<Dropdown.Item variant="error">
<Dropdown.ItemIcon as={RiLogoutBoxRLine} />
Logout
</Dropdown.Item>
</Dropdown.Group>
</Dropdown.Content>
</Dropdown.Root>
With Header and Footer
Dropdown with a user profile header, menu groups, and footer text.
<Dropdown.Root>
<Dropdown.Trigger asChild>
<Button.Root variant="neutral" mode="stroke">
Open Dropdown
</Button.Root>
</Dropdown.Trigger>
<Dropdown.Content align="start">
<div className="flex items-center gap-3 p-2">
<Avatar.Root size="40" />
<div className="flex-1">
<div className="text-label-sm text-text-strong-950">Wei Chen</div>
<div className="text-paragraph-xs text-text-sub-600 mt-1">wei@alignui.com</div>
</div>
<Badge.Root variant="light" color="green" size="medium">PRO</Badge.Root>
</div>
<Divider.Root variant="line-spacing" />
<Dropdown.Group>
<Dropdown.Item>
<Dropdown.ItemIcon as={RiPulseLine} />
Activity
</Dropdown.Item>
<Dropdown.Item>
<Dropdown.ItemIcon as={RiLayoutGridLine} />
Integrations
</Dropdown.Item>
<Dropdown.Item>
<Dropdown.ItemIcon as={RiSettings2Line} />
Settings
</Dropdown.Item>
</Dropdown.Group>
<Divider.Root variant="line-spacing" />
<Dropdown.Group>
<Dropdown.Item>
<Dropdown.ItemIcon as={RiAddLine} />
Add Account
</Dropdown.Item>
<Dropdown.Item>
<Dropdown.ItemIcon as={RiLogoutBoxRLine} />
Logout
</Dropdown.Item>
</Dropdown.Group>
<div className="text-paragraph-sm text-text-soft-400 p-2">
v.1.5.69 · Terms & Conditions
</div>
</Dropdown.Content>
</Dropdown.Root>
With Error Item
Dropdown with a destructive action styled in error red, separated from other items.
<Dropdown.Root>
<Dropdown.Trigger asChild>
<Button.Root variant="neutral" mode="stroke">Actions</Button.Root>
</Dropdown.Trigger>
<Dropdown.Content align="start">
<Dropdown.Group>
<Dropdown.Item>
<Dropdown.ItemIcon as={RiPencilLine} />
Edit
</Dropdown.Item>
<Dropdown.Item>
<Dropdown.ItemIcon as={RiSettings2Line} />
Settings
</Dropdown.Item>
</Dropdown.Group>
<Divider.Root variant="line-spacing" />
<Dropdown.Group>
<Dropdown.Item variant="error">
<Dropdown.ItemIcon as={RiDeleteBinLine} />
Delete
</Dropdown.Item>
</Dropdown.Group>
</Dropdown.Content>
</Dropdown.Root>
With Disabled Items
Dropdown with a disabled item that cannot be selected.
<Dropdown.Root>
<Dropdown.Trigger asChild>
<Button.Root variant="neutral" mode="stroke">
Open Dropdown
</Button.Root>
</Dropdown.Trigger>
<Dropdown.Content align="start">
<Dropdown.Group>
<Dropdown.Item>
<Dropdown.ItemIcon as={RiPencilLine} />
Edit
</Dropdown.Item>
<Dropdown.Item disabled>
<Dropdown.ItemIcon as={RiRocketLine} />
Publish (unavailable)
</Dropdown.Item>
<Dropdown.Item>
<Dropdown.ItemIcon as={RiSettings2Line} />
Settings
</Dropdown.Item>
</Dropdown.Group>
</Dropdown.Content>
</Dropdown.Root>
Sub Menu
Dropdown with nested sub-menus for hierarchical navigation.
<Dropdown.Root>
<Dropdown.Trigger asChild>
<Button.Root variant="neutral" mode="stroke">Open</Button.Root>
</Dropdown.Trigger>
<Dropdown.Content align="start">
<Dropdown.Group>
<Dropdown.Item>
<Dropdown.ItemIcon as={RiAddLine} />
New post creation
</Dropdown.Item>
<Dropdown.MenuSub>
<Dropdown.MenuSubTrigger>
<Dropdown.ItemIcon as={RiFileList2Line} />
Template Options
</Dropdown.MenuSubTrigger>
<Dropdown.MenuSubContent>
<Dropdown.Item>
<Dropdown.ItemIcon as={RiPencilLine} />
Draft Post
</Dropdown.Item>
<Dropdown.Item>
<Dropdown.ItemIcon as={RiRocketLine} />
Publish Now
</Dropdown.Item>
<Dropdown.Item>
<Dropdown.ItemIcon as={RiCalendar2Line} />
Schedule Post
</Dropdown.Item>
</Dropdown.MenuSubContent>
</Dropdown.MenuSub>
</Dropdown.Group>
</Dropdown.Content>
</Dropdown.Root>
Composed
The composed shorthand renders a full dropdown from a groups array. Separators are added between groups automatically.
<Dropdown.Composed
groups={[
{
items: [
{ label: 'Activity', icon: RiPulseLine, onSelect: () => console.log('Activity') },
{ label: 'Integrations', icon: RiLayoutGridLine, onSelect: () => console.log('Integrations') },
{ label: 'Settings', icon: RiSettings2Line, onSelect: () => console.log('Settings') },
],
},
{
items: [
{ label: 'Add Account', icon: RiAddLine },
{ label: 'Logout', icon: RiLogoutBoxRLine, variant: 'error' },
],
},
]}
>
<Button.Root variant="neutral" mode="stroke">
Open Composed
</Button.Root>
</Dropdown.Composed>
Composed with Header & Footer
Composed dropdown with a user profile header and footer text.
<Dropdown.Composed
groups={[
{
items: [
{ label: 'Activity', icon: RiPulseLine },
{ label: 'Integrations', icon: RiLayoutGridLine },
{ label: 'Settings', icon: RiSettings2Line },
],
},
{
items: [
{ label: 'Add Account', icon: RiAddLine },
{ label: 'Logout', icon: RiLogoutBoxRLine, variant: 'error' },
],
},
]}
header={
<>
<div className="flex items-center gap-3 p-2">
<Avatar.Root size="40" />
<div className="flex-1">
<div className="text-label-sm text-text-strong-950">Wei Chen</div>
<div className="text-paragraph-xs text-text-sub-600 mt-1">wei@alignui.com</div>
</div>
<Badge.Root variant="light" color="green" size="medium">PRO</Badge.Root>
</div>
<Divider.Root variant="line-spacing" />
</>
}
footer={
<div className="text-paragraph-sm text-text-soft-400 p-2">
v.1.5.69 · Terms & Conditions
</div>
}
>
<Button.Root variant="neutral" mode="stroke">
Profile Menu
</Button.Root>
</Dropdown.Composed>
Composed with Labels
Composed dropdown with group labels for section headings.
<Dropdown.Composed
groups={[
{
label: 'Account',
items: [
{ label: 'Activity', icon: RiPulseLine },
{ label: 'Settings', icon: RiSettings2Line },
],
},
{
label: 'Danger zone',
items: [
{ label: 'Delete account', icon: RiDeleteBinLine, variant: 'error' },
],
},
]}
>
<Button.Root variant="neutral" mode="stroke">
Account
</Button.Root>
</Dropdown.Composed>
Composed with Links
Composed dropdown with href items that render as anchor tags.
<Dropdown.Composed
groups={[
{
items: [
{ label: 'Dashboard', icon: RiLayoutGridLine, href: '#dashboard' },
{ label: 'Settings', icon: RiSettings2Line, href: '#settings' },
{ label: 'Documentation', icon: RiGlobeLine, href: 'https://example.com' },
],
},
]}
>
<Button.Root variant="neutral" mode="stroke">
Navigation
</Button.Root>
</Dropdown.Composed>
API Reference
Dropdown.Root
The root dropdown container. Passes through Radix DropdownMenu.Root props.
| Prop | Type | Default | Description |
|---|---|---|---|
open | boolean | - | Controlled open state |
onOpenChange | (open: boolean) => void | - | Callback when open state changes |
Dropdown.Trigger
The element that opens the dropdown.
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | boolean | false | Render as child element using Radix Slot |
Dropdown.Content
The dropdown content panel with animations.
| Prop | Type | Default | Description |
|---|---|---|---|
side | 'top' | 'right' | 'bottom' | 'left' | 'bottom' | The preferred side to position the dropdown |
align | 'start' | 'center' | 'end' | 'center' | The alignment relative to the trigger |
sideOffset | number | 8 | Distance in pixels from the trigger |
Dropdown.Item
A selectable dropdown menu item.
| Prop | Type | Default | Description |
|---|---|---|---|
variant | 'default' | 'error' | 'default' | Visual variant. Error renders text and icon in red. |
inset | boolean | false | Add left padding for alignment with icon items |
disabled | boolean | false | Disables the item |
onSelect | () => void | - | Called when the item is selected |
Dropdown.ItemIcon
Polymorphic icon displayed inside a dropdown item. Inherits error color from variant.
| Prop | Type | Default | Description |
|---|---|---|---|
as | React.ElementType | - | The icon component to render |
Dropdown.Group
Groups related dropdown items.
| Prop | Type | Default | Description |
|---|
Dropdown.Label
An uppercase label for a group of dropdown items.
| Prop | Type | Default | Description |
|---|
Dropdown.MenuSub
Container for a sub-menu.
| Prop | Type | Default | Description |
|---|
Dropdown.MenuSubTrigger
The item that opens a sub-menu. Shows a right arrow indicator.
| Prop | Type | Default | Description |
|---|---|---|---|
inset | boolean | false | Add left padding for alignment |
Dropdown.MenuSubContent
The sub-menu content panel.
| Prop | Type | Default | Description |
|---|
Dropdown.Composed
A composed shorthand that renders a full dropdown from a groups array. Handles trigger, content, items, separators, icons, and variants automatically.
| Prop | Type | Default | Description |
|---|---|---|---|
children | React.ReactNode | - | The trigger element. Rendered via asChild. |
groups | DropdownMenuGroup[] | - | Array of menu groups. Each group has an optional label and an items array. Each item has label, optional icon, variant, disabled, onSelect, and href. |
align | 'start' | 'center' | 'end' | 'start' | Content alignment relative to trigger |
side | 'top' | 'right' | 'bottom' | 'left' | - | Content side relative to trigger |
sideOffset | number | - | Distance in pixels from the trigger |
contentClassName | string | - | Additional className for the content panel |
header | React.ReactNode | - | Header content rendered above menu items |
footer | React.ReactNode | - | Footer content rendered below menu items |
open | boolean | - | Controlled open state |
onOpenChange | (open: boolean) => void | - | Callback when open state changes |