A dropdown displays a list of actions or options that a user can choose.
Import
import { Dropdown } from "heroui-solid";Usage
import { Button, Dropdown, Label } from "heroui-solid"
export function Default() {
return (
<Dropdown>
<Button aria-label="Menu" variant="secondary">
Actions
</Button>
<Dropdown.Popover>
<Dropdown.Menu onAction={(key) => console.log(`Selected: ${key}`)}>
<Dropdown.Item id="new-file" textValue="New file">
<Label>New file</Label>
</Dropdown.Item>
<Dropdown.Item id="copy-link" textValue="Copy link">
<Label>Copy link</Label>
</Dropdown.Item>
<Dropdown.Item id="edit-file" textValue="Edit file">
<Label>Edit file</Label>
</Dropdown.Item>
<Dropdown.Item
id="delete-file"
textValue="Delete file"
variant="danger"
>
<Label>Delete file</Label>
</Dropdown.Item>
</Dropdown.Menu>
</Dropdown.Popover>
</Dropdown>
)
}Anatomy
Import the Dropdown component and access all parts using dot notation.
import { Dropdown, Label, Description, Header, Kbd, Separator } from "heroui-solid";
export default () => (
<Dropdown>
<Dropdown.Trigger />
<Dropdown.Popover>
<Dropdown.Menu>
<Dropdown.Item>
<Label />
<Description />
<Kbd slot="keyboard" />
<Dropdown.ItemIndicator />
</Dropdown.Item>
<Separator />
<Dropdown.Section>
<Header />
<Dropdown.Item />
</Dropdown.Section>
<Dropdown.SubmenuTrigger>
<Dropdown.Item>
<Label />
<Dropdown.SubmenuIndicator />
</Dropdown.Item>
<Dropdown.Popover>
<Dropdown.Menu>
<Dropdown.Item />
</Dropdown.Menu>
</Dropdown.Popover>
</Dropdown.SubmenuTrigger>
</Dropdown.Menu>
</Dropdown.Popover>
</Dropdown>
);With Single Selection
import { Button, Dropdown, Header, Label, type Selection } from "heroui-solid"
import { createSignal } from "solid-js"
export function WithSingleSelection() {
const [selected, setSelected] = createSignal<Selection>(new Set(["apple"]))
return (
<Dropdown>
<Button aria-label="Menu" variant="secondary">
Fruit
</Button>
<Dropdown.Popover class="min-w-[256px]">
<Dropdown.Menu
selectedKeys={selected()}
selectionMode="single"
onSelectionChange={setSelected}
>
<Dropdown.Section>
<Header>Select a fruit</Header>
<Dropdown.Item id="apple" textValue="Apple">
<Dropdown.ItemIndicator />
<Label>Apple</Label>
</Dropdown.Item>
<Dropdown.Item id="banana" textValue="Banana">
<Dropdown.ItemIndicator />
<Label>Banana</Label>
</Dropdown.Item>
<Dropdown.Item id="cherry" textValue="Cherry">
<Dropdown.ItemIndicator />
<Label>Cherry</Label>
</Dropdown.Item>
</Dropdown.Section>
<Dropdown.Item id="orange" textValue="Orange">
<Dropdown.ItemIndicator />
<Label>Orange</Label>
</Dropdown.Item>
<Dropdown.Item id="pear" textValue="Pear">
<Dropdown.ItemIndicator />
<Label>Pear</Label>
</Dropdown.Item>
</Dropdown.Menu>
</Dropdown.Popover>
</Dropdown>
)
}Single With Custom Indicator
import { Button, Dropdown, Header, Label, type Selection } from "heroui-solid"
import { createSignal } from "solid-js"
export function SingleWithCustomIndicator() {
const [selected, setSelected] = createSignal<Selection>(new Set(["apple"]))
const CustomCheckmarkIcon = () => (
// biome-ignore lint/a11y/noSvgWithoutTitle: decorative, inside the aria-hidden indicator (mirrors upstream)
<svg
height="16"
viewBox="0 0 16 16"
width="16"
xmlns="http://www.w3.org/2000/svg"
>
<path
class="text-accent-soft-foreground"
clip-rule="evenodd"
d="M8 15A7 7 0 1 0 8 1a7 7 0 0 0 0 14m3.1-8.55a.75.75 0 1 0-1.2-.9L7.419 8.858L6.03 7.47a.75.75 0 0 0-1.06 1.06l2 2a.75.75 0 0 0 1.13-.08z"
fill="currentColor"
fill-rule="evenodd"
/>
</svg>
)
return (
<Dropdown>
<Button aria-label="Menu" variant="secondary">
Fruits
</Button>
<Dropdown.Popover class="min-w-[256px]">
<Dropdown.Menu
selectedKeys={selected()}
selectionMode="single"
onSelectionChange={setSelected}
>
<Dropdown.Section>
<Header>Select a fruit</Header>
<Dropdown.Item id="apple" textValue="Apple">
<Dropdown.ItemIndicator>
{(state) => (state.isSelected ? CustomCheckmarkIcon() : null)}
</Dropdown.ItemIndicator>
<Label>Apple</Label>
</Dropdown.Item>
<Dropdown.Item id="banana" textValue="Banana">
<Dropdown.ItemIndicator>
{(state) => (state.isSelected ? CustomCheckmarkIcon() : null)}
</Dropdown.ItemIndicator>
<Label>Banana</Label>
</Dropdown.Item>
<Dropdown.Item id="cherry" textValue="Cherry">
<Dropdown.ItemIndicator>
{(state) => (state.isSelected ? CustomCheckmarkIcon() : null)}
</Dropdown.ItemIndicator>
<Label>Cherry</Label>
</Dropdown.Item>
</Dropdown.Section>
<Dropdown.Item id="orange" textValue="Orange">
<Dropdown.ItemIndicator>
{(state) => (state.isSelected ? CustomCheckmarkIcon() : null)}
</Dropdown.ItemIndicator>
<Label>Orange</Label>
</Dropdown.Item>
<Dropdown.Item id="pear" textValue="Pear">
<Dropdown.ItemIndicator>
{(state) => (state.isSelected ? CustomCheckmarkIcon() : null)}
</Dropdown.ItemIndicator>
<Label>Pear</Label>
</Dropdown.Item>
</Dropdown.Menu>
</Dropdown.Popover>
</Dropdown>
)
}With Multiple Selection
import { Button, Dropdown, Header, Label, type Selection } from "heroui-solid"
import { createSignal } from "solid-js"
export function WithMultipleSelection() {
const [selected, setSelected] = createSignal<Selection>(new Set(["apple"]))
return (
<Dropdown>
<Button aria-label="Menu" variant="secondary">
Preferred Fruits
</Button>
<Dropdown.Popover class="min-w-[256px]">
<Dropdown.Menu
selectedKeys={selected()}
selectionMode="multiple"
onSelectionChange={setSelected}
>
<Dropdown.Section>
<Header>Select a fruit</Header>
<Dropdown.Item id="apple" textValue="Apple">
<Dropdown.ItemIndicator />
<Label>Apple</Label>
</Dropdown.Item>
<Dropdown.Item id="banana" textValue="Banana">
<Dropdown.ItemIndicator />
<Label>Banana</Label>
</Dropdown.Item>
<Dropdown.Item id="cherry" textValue="Cherry">
<Dropdown.ItemIndicator />
<Label>Cherry</Label>
</Dropdown.Item>
</Dropdown.Section>
<Dropdown.Item id="orange" textValue="Orange">
<Dropdown.ItemIndicator />
<Label>Orange</Label>
</Dropdown.Item>
<Dropdown.Item id="pear" textValue="Pear">
<Dropdown.ItemIndicator />
<Label>Pear</Label>
</Dropdown.Item>
</Dropdown.Menu>
</Dropdown.Popover>
</Dropdown>
)
}With Section Level Selection
import {
Button,
Dropdown,
Header,
Kbd,
Label,
type Selection,
Separator
} from "heroui-solid"
import { createSignal } from "solid-js"
export function WithSectionLevelSelection() {
const [textStyles, setTextStyles] = createSignal<Selection>(
new Set(["bold", "italic"])
)
const [textAlignment, setTextAlignment] = createSignal<Selection>(
new Set(["left"])
)
return (
<Dropdown>
<Button aria-label="Menu" variant="secondary">
Styles
</Button>
<Dropdown.Popover class="min-w-[256px]">
<Dropdown.Menu>
<Dropdown.Section>
<Header>Actions</Header>
<Dropdown.Item id="cut" textValue="Cut">
<Label>Cut</Label>
<Kbd class="ms-auto" slot="keyboard" variant="light">
<Kbd.Abbr keyValue="command" />
<Kbd.Content>X</Kbd.Content>
</Kbd>
</Dropdown.Item>
<Dropdown.Item id="copy" textValue="Copy">
<Label>Copy</Label>
<Kbd class="ms-auto" slot="keyboard" variant="light">
<Kbd.Abbr keyValue="command" />
<Kbd.Content>C</Kbd.Content>
</Kbd>
</Dropdown.Item>
<Dropdown.Item id="paste" textValue="Paste">
<Label>Paste</Label>
<Kbd class="ms-auto" slot="keyboard" variant="light">
<Kbd.Abbr keyValue="command" />
<Kbd.Content>U</Kbd.Content>
</Kbd>
</Dropdown.Item>
</Dropdown.Section>
<Separator />
<Dropdown.Section
selectedKeys={textStyles()}
selectionMode="multiple"
onSelectionChange={setTextStyles}
>
<Header>Text Style</Header>
<Dropdown.Item id="bold" textValue="Bold">
<Dropdown.ItemIndicator />
<Label>Bold</Label>
<Kbd class="ms-auto" slot="keyboard" variant="light">
<Kbd.Abbr keyValue="command" />
<Kbd.Content>B</Kbd.Content>
</Kbd>
</Dropdown.Item>
<Dropdown.Item id="italic" textValue="Italic">
<Dropdown.ItemIndicator />
<Label>Italic</Label>
<Kbd class="ms-auto" slot="keyboard" variant="light">
<Kbd.Abbr keyValue="command" />
<Kbd.Content>I</Kbd.Content>
</Kbd>
</Dropdown.Item>
<Dropdown.Item id="underline" textValue="Underline">
<Dropdown.ItemIndicator />
<Label>Underline</Label>
<Kbd class="ms-auto" slot="keyboard" variant="light">
<Kbd.Abbr keyValue="command" />
<Kbd.Content>U</Kbd.Content>
</Kbd>
</Dropdown.Item>
</Dropdown.Section>
<Separator />
<Dropdown.Section
selectedKeys={textAlignment()}
selectionMode="single"
onSelectionChange={setTextAlignment}
>
<Header>Text Alignment</Header>
<Dropdown.Item id="left" textValue="Left">
<Dropdown.ItemIndicator type="dot" />
<Label>Left</Label>
<Kbd class="ms-auto" slot="keyboard" variant="light">
<Kbd.Abbr keyValue="alt" />
<Kbd.Content>A</Kbd.Content>
</Kbd>
</Dropdown.Item>
<Dropdown.Item id="center" textValue="Center">
<Dropdown.ItemIndicator type="dot" />
<Label>Center</Label>
<Kbd class="ms-auto" slot="keyboard" variant="light">
<Kbd.Abbr keyValue="alt" />
<Kbd.Content>H</Kbd.Content>
</Kbd>
</Dropdown.Item>
<Dropdown.Item id="right" textValue="Right">
<Dropdown.ItemIndicator type="dot" />
<Label>Right</Label>
<Kbd class="ms-auto" slot="keyboard" variant="light">
<Kbd.Abbr keyValue="alt" />
<Kbd.Content>D</Kbd.Content>
</Kbd>
</Dropdown.Item>
</Dropdown.Section>
</Dropdown.Menu>
</Dropdown.Popover>
</Dropdown>
)
}With Keyboard Shortcuts
import { Button, Dropdown, Kbd, Label } from "heroui-solid"
export function WithKeyboardShortcuts() {
return (
<Dropdown>
<Button aria-label="Menu" variant="secondary">
Actions
</Button>
<Dropdown.Popover>
<Dropdown.Menu onAction={(key) => console.log(`Selected: ${key}`)}>
<Dropdown.Item id="new" textValue="New">
<Label>New</Label>
<Kbd class="ms-auto" slot="keyboard" variant="light">
<Kbd.Abbr keyValue="command" />
<Kbd.Content>N</Kbd.Content>
</Kbd>
</Dropdown.Item>
<Dropdown.Item id="open" textValue="Open">
<Label>Open</Label>
<Kbd class="ms-auto" slot="keyboard" variant="light">
<Kbd.Abbr keyValue="command" />
<Kbd.Content>O</Kbd.Content>
</Kbd>
</Dropdown.Item>
<Dropdown.Item id="save" textValue="Save">
<Label>Save</Label>
<Kbd class="ms-auto" slot="keyboard" variant="light">
<Kbd.Abbr keyValue="command" />
<Kbd.Content>S</Kbd.Content>
</Kbd>
</Dropdown.Item>
<Dropdown.Item id="delete" textValue="Delete" variant="danger">
<Label>Delete</Label>
<Kbd class="ms-auto" slot="keyboard" variant="light">
<Kbd.Abbr keyValue="command" />
<Kbd.Abbr keyValue="shift" />
<Kbd.Content>D</Kbd.Content>
</Kbd>
</Dropdown.Item>
</Dropdown.Menu>
</Dropdown.Popover>
</Dropdown>
)
}With Icons
import {
FloppyDisk,
FolderOpen,
SquarePlus,
TrashBin
} from "gravity-icons-solid"
import { Button, Dropdown, Kbd, Label } from "heroui-solid"
export function WithIcons() {
return (
<Dropdown>
<Button aria-label="Menu" variant="secondary">
Actions
</Button>
<Dropdown.Popover>
<Dropdown.Menu onAction={(key) => console.log(`Selected: ${key}`)}>
<Dropdown.Item id="new-file" textValue="New file">
<SquarePlus class="size-4 shrink-0 text-muted" />
<Label>New file</Label>
<Kbd class="ms-auto" slot="keyboard" variant="light">
<Kbd.Abbr keyValue="command" />
<Kbd.Content>N</Kbd.Content>
</Kbd>
</Dropdown.Item>
<Dropdown.Item id="open-file" textValue="Open file">
<FolderOpen class="size-4 shrink-0 text-muted" />
<Label>Open file</Label>
<Kbd class="ms-auto" slot="keyboard" variant="light">
<Kbd.Abbr keyValue="command" />
<Kbd.Content>O</Kbd.Content>
</Kbd>
</Dropdown.Item>
<Dropdown.Item id="save-file" textValue="Save file">
<FloppyDisk class="size-4 shrink-0 text-muted" />
<Label>Save file</Label>
<Kbd class="ms-auto" slot="keyboard" variant="light">
<Kbd.Abbr keyValue="command" />
<Kbd.Content>S</Kbd.Content>
</Kbd>
</Dropdown.Item>
<Dropdown.Item
id="delete-file"
textValue="Delete file"
variant="danger"
>
<TrashBin class="size-4 shrink-0 text-danger" />
<Label>Delete file</Label>
<Kbd class="ms-auto" slot="keyboard" variant="light">
<Kbd.Abbr keyValue="command" />
<Kbd.Abbr keyValue="shift" />
<Kbd.Content>D</Kbd.Content>
</Kbd>
</Dropdown.Item>
</Dropdown.Menu>
</Dropdown.Popover>
</Dropdown>
)
}Long Press Trigger
import { Button, Dropdown, Label } from "heroui-solid"
export function LongPressTrigger() {
return (
<Dropdown trigger="longPress">
<Button aria-label="Menu" variant="secondary">
Long Press
</Button>
<Dropdown.Popover>
<Dropdown.Menu>
<Dropdown.Item id="new-file" textValue="New file">
<Label>New file</Label>
</Dropdown.Item>
<Dropdown.Item id="open-file" textValue="Open file">
<Label>Open file</Label>
</Dropdown.Item>
<Dropdown.Item id="save-file" textValue="Save file">
<Label>Save file</Label>
</Dropdown.Item>
<Dropdown.Item
id="delete-file"
textValue="Delete file"
variant="danger"
>
<Label>Delete file</Label>
</Dropdown.Item>
</Dropdown.Menu>
</Dropdown.Popover>
</Dropdown>
)
}With Descriptions
import {
FloppyDisk,
FolderOpen,
SquarePlus,
TrashBin
} from "gravity-icons-solid"
import { Button, Description, Dropdown, Kbd, Label } from "heroui-solid"
export function WithDescriptions() {
return (
<Dropdown>
<Button aria-label="Menu" variant="secondary">
Actions
</Button>
<Dropdown.Popover>
<Dropdown.Menu onAction={(key) => console.log(`Selected: ${key}`)}>
<Dropdown.Item id="new-file" textValue="New file">
<div class="flex h-8 items-start justify-center pt-px">
<SquarePlus class="size-4 shrink-0 text-muted" />
</div>
<div class="flex flex-col">
<Label>New file</Label>
<Description>Create a new file</Description>
</div>
<Kbd class="ms-auto" slot="keyboard" variant="light">
<Kbd.Abbr keyValue="command" />
<Kbd.Content>N</Kbd.Content>
</Kbd>
</Dropdown.Item>
<Dropdown.Item id="open-file" textValue="Open file">
<div class="flex h-8 items-start justify-center pt-px">
<FolderOpen class="size-4 shrink-0 text-muted" />
</div>
<div class="flex flex-col">
<Label>Open file</Label>
<Description>Open an existing file</Description>
</div>
<Kbd class="ms-auto" slot="keyboard" variant="light">
<Kbd.Abbr keyValue="command" />
<Kbd.Content>O</Kbd.Content>
</Kbd>
</Dropdown.Item>
<Dropdown.Item id="save-file" textValue="Save file">
<div class="flex h-8 items-start justify-center pt-px">
<FloppyDisk class="size-4 shrink-0 text-muted" />
</div>
<div class="flex flex-col">
<Label>Save file</Label>
<Description>Save the current file</Description>
</div>
<Kbd class="ms-auto" slot="keyboard" variant="light">
<Kbd.Abbr keyValue="command" />
<Kbd.Content>S</Kbd.Content>
</Kbd>
</Dropdown.Item>
<Dropdown.Item
id="delete-file"
textValue="Delete file"
variant="danger"
>
<div class="flex h-8 items-start justify-center pt-px">
<TrashBin class="size-4 shrink-0 text-danger" />
</div>
<div class="flex flex-col">
<Label>Delete file</Label>
<Description>Move to trash</Description>
</div>
<Kbd class="ms-auto" slot="keyboard" variant="light">
<Kbd.Abbr keyValue="command" />
<Kbd.Abbr keyValue="shift" />
<Kbd.Content>D</Kbd.Content>
</Kbd>
</Dropdown.Item>
</Dropdown.Menu>
</Dropdown.Popover>
</Dropdown>
)
}With Sections
import {
EllipsisVertical,
Pencil,
SquarePlus,
TrashBin
} from "gravity-icons-solid"
import {
Button,
Description,
Dropdown,
Header,
Kbd,
Label,
Separator
} from "heroui-solid"
export function WithSections() {
return (
<Dropdown>
<Button isIconOnly aria-label="Menu" variant="secondary">
<EllipsisVertical class="outline-none" />
</Button>
<Dropdown.Popover>
<Dropdown.Menu onAction={(key) => console.log(`Selected: ${key}`)}>
<Dropdown.Section>
<Header>Actions</Header>
<Dropdown.Item id="new-file" textValue="New file">
<div class="flex h-8 items-start justify-center pt-px">
<SquarePlus class="size-4 shrink-0 text-muted" />
</div>
<div class="flex flex-col">
<Label>New file</Label>
<Description>Create a new file</Description>
</div>
<Kbd class="ms-auto" slot="keyboard" variant="light">
<Kbd.Abbr keyValue="command" />
<Kbd.Content>N</Kbd.Content>
</Kbd>
</Dropdown.Item>
<Dropdown.Item id="edit-file" textValue="Edit file">
<div class="flex h-8 items-start justify-center pt-px">
<Pencil class="size-4 shrink-0 text-muted" />
</div>
<div class="flex flex-col">
<Label>Edit file</Label>
<Description>Make changes</Description>
</div>
<Kbd class="ms-auto" slot="keyboard" variant="light">
<Kbd.Abbr keyValue="command" />
<Kbd.Content>E</Kbd.Content>
</Kbd>
</Dropdown.Item>
</Dropdown.Section>
<Separator />
<Dropdown.Section>
<Header>Danger zone</Header>
<Dropdown.Item
id="delete-file"
textValue="Delete file"
variant="danger"
>
<div class="flex h-8 items-start justify-center pt-px">
<TrashBin class="size-4 shrink-0 text-danger" />
</div>
<div class="flex flex-col">
<Label>Delete file</Label>
<Description>Move to trash</Description>
</div>
<Kbd class="ms-auto" slot="keyboard" variant="light">
<Kbd.Abbr keyValue="command" />
<Kbd.Abbr keyValue="shift" />
<Kbd.Content>D</Kbd.Content>
</Kbd>
</Dropdown.Item>
</Dropdown.Section>
</Dropdown.Menu>
</Dropdown.Popover>
</Dropdown>
)
}With Disabled Items
import { Bars, Pencil, SquarePlus, TrashBin } from "gravity-icons-solid"
import {
Button,
Description,
Dropdown,
Header,
Kbd,
Label,
Separator
} from "heroui-solid"
export function WithDisabledItems() {
return (
<Dropdown>
<Button isIconOnly aria-label="Menu" variant="secondary">
<Bars class="outline-none" />
</Button>
<Dropdown.Popover class="min-w-[220px]">
<Dropdown.Menu
disabledKeys={["delete-file"]}
onAction={(key) => console.log(`Selected: ${key}`)}
>
<Dropdown.Section>
<Header>Actions</Header>
<Dropdown.Item id="new-file" textValue="New file">
<div class="flex h-8 items-start justify-center pt-px">
<SquarePlus class="size-4 shrink-0 text-muted" />
</div>
<div class="flex flex-col">
<Label>New file</Label>
<Description>Create a new file</Description>
</div>
<Kbd class="ms-auto" slot="keyboard" variant="light">
<Kbd.Abbr keyValue="command" />
<Kbd.Content>N</Kbd.Content>
</Kbd>
</Dropdown.Item>
<Dropdown.Item id="edit-file" textValue="Edit file">
<div class="flex h-8 items-start justify-center pt-px">
<Pencil class="size-4 shrink-0 text-muted" />
</div>
<div class="flex flex-col">
<Label>Edit file</Label>
<Description>Make changes</Description>
</div>
<Kbd class="ms-auto" slot="keyboard" variant="light">
<Kbd.Abbr keyValue="command" />
<Kbd.Content>E</Kbd.Content>
</Kbd>
</Dropdown.Item>
</Dropdown.Section>
<Separator />
<Dropdown.Section>
<Header>Danger zone</Header>
<Dropdown.Item
id="delete-file"
textValue="Delete file"
variant="danger"
>
<div class="flex h-8 items-start justify-center pt-px">
<TrashBin class="size-4 shrink-0 text-danger" />
</div>
<div class="flex flex-col">
<Label>Delete file</Label>
<Description>Move to trash</Description>
</div>
<Kbd class="ms-auto" slot="keyboard" variant="light">
<Kbd.Abbr keyValue="command" />
<Kbd.Abbr keyValue="shift" />
<Kbd.Content>D</Kbd.Content>
</Kbd>
</Dropdown.Item>
</Dropdown.Section>
</Dropdown.Menu>
</Dropdown.Popover>
</Dropdown>
)
}With Submenus
import { Button, Dropdown, Label } from "heroui-solid"
export function WithSubmenus() {
return (
<Dropdown>
<Button aria-label="Menu" variant="secondary">
Share
</Button>
<Dropdown.Popover>
<Dropdown.Menu onAction={(key) => console.log(`Selected: ${key}`)}>
<Dropdown.Item id="copy-link" textValue="Copy Link">
<Label>Copy Link</Label>
</Dropdown.Item>
<Dropdown.Item id="facebook" textValue="Facebook">
<Label>Facebook</Label>
</Dropdown.Item>
<Dropdown.Item id="twitter" textValue="Twitter">
<Label>X / Twitter</Label>
</Dropdown.Item>
<Dropdown.SubmenuTrigger>
<Dropdown.Item id="share" textValue="Share">
<Label>Other</Label>
<Dropdown.SubmenuIndicator />
</Dropdown.Item>
<Dropdown.Popover>
<Dropdown.Menu>
<Dropdown.Item id="whatsapp" textValue="WhatsApp">
<Label>WhatsApp</Label>
</Dropdown.Item>
<Dropdown.Item id="telegram" textValue="Telegram">
<Label>Telegram</Label>
</Dropdown.Item>
<Dropdown.Item id="discord" textValue="Discord">
<Label>Discord</Label>
</Dropdown.Item>
<Dropdown.SubmenuTrigger>
<Dropdown.Item id="email" textValue="Email">
<Label>Email</Label>
<Dropdown.SubmenuIndicator />
</Dropdown.Item>
<Dropdown.Popover>
<Dropdown.Menu>
<Dropdown.Item id="work" textValue="Work email">
<Label>Work email</Label>
</Dropdown.Item>
<Dropdown.Item id="personal" textValue="Personal email">
<Label>Personal email</Label>
</Dropdown.Item>
</Dropdown.Menu>
</Dropdown.Popover>
</Dropdown.SubmenuTrigger>
</Dropdown.Menu>
</Dropdown.Popover>
</Dropdown.SubmenuTrigger>
</Dropdown.Menu>
</Dropdown.Popover>
</Dropdown>
)
}With Custom Submenu Indicator
import { ArrowRight } from "gravity-icons-solid"
import { Button, Dropdown, Label } from "heroui-solid"
export function WithCustomSubmenuIndicator() {
return (
<Dropdown>
<Button aria-label="Menu" variant="secondary">
Share
</Button>
<Dropdown.Popover>
<Dropdown.Menu onAction={(key) => console.log(`Selected: ${key}`)}>
<Dropdown.Item id="copy-link" textValue="Copy Link">
<Label>Copy Link</Label>
</Dropdown.Item>
<Dropdown.Item id="facebook" textValue="Facebook">
<Label>Facebook</Label>
</Dropdown.Item>
<Dropdown.SubmenuTrigger>
<Dropdown.Item id="share" textValue="Share">
<Label>More options</Label>
<Dropdown.SubmenuIndicator>
<ArrowRight class="size-3.5 text-muted" />
</Dropdown.SubmenuIndicator>
</Dropdown.Item>
<Dropdown.Popover>
<Dropdown.Menu>
<Dropdown.Item id="whatsapp" textValue="WhatsApp">
<Label>WhatsApp</Label>
</Dropdown.Item>
<Dropdown.Item id="telegram" textValue="Telegram">
<Label>Telegram</Label>
</Dropdown.Item>
<Dropdown.SubmenuTrigger>
<Dropdown.Item id="email" textValue="Email">
<Label>Email</Label>
<Dropdown.SubmenuIndicator>
{/* biome-ignore lint/a11y/noSvgWithoutTitle: decorative, inside the aria-hidden indicator (mirrors upstream) */}
<svg
class="size-3.5 text-muted"
fill="none"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
viewBox="0 0 24 24"
>
<path d="M9 18l6-6-6-6" />
</svg>
</Dropdown.SubmenuIndicator>
</Dropdown.Item>
<Dropdown.Popover>
<Dropdown.Menu>
<Dropdown.Item id="work" textValue="Work email">
<Label>Work email</Label>
</Dropdown.Item>
<Dropdown.Item id="personal" textValue="Personal email">
<Label>Personal email</Label>
</Dropdown.Item>
</Dropdown.Menu>
</Dropdown.Popover>
</Dropdown.SubmenuTrigger>
<Dropdown.Item id="discord" textValue="Discord">
<Label>Discord</Label>
</Dropdown.Item>
</Dropdown.Menu>
</Dropdown.Popover>
</Dropdown.SubmenuTrigger>
<Dropdown.SubmenuTrigger>
<Dropdown.Item id="other" textValue="Other">
<Label>Other (default indicator)</Label>
<Dropdown.SubmenuIndicator />
</Dropdown.Item>
<Dropdown.Popover>
<Dropdown.Menu>
<Dropdown.Item id="sms" textValue="SMS">
<Label>SMS</Label>
</Dropdown.Item>
</Dropdown.Menu>
</Dropdown.Popover>
</Dropdown.SubmenuTrigger>
</Dropdown.Menu>
</Dropdown.Popover>
</Dropdown>
)
}Controlled
Selected: bold
import { Button, Dropdown, Label, type Selection } from "heroui-solid"
import { createSignal } from "solid-js"
export function Controlled() {
const [selected, setSelected] = createSignal<Selection>(new Set(["bold"]))
const selectedItems = () => Array.from(selected())
return (
<div class="flex min-w-sm flex-col items-center justify-center gap-4">
<p class="text-sm text-muted">
Selected:{" "}
{selectedItems().length > 0 ? selectedItems().join(", ") : "None"}
</p>
<Dropdown>
<Button aria-label="Menu" variant="secondary">
Actions
</Button>
<Dropdown.Popover>
<Dropdown.Menu
selectedKeys={selected()}
selectionMode="multiple"
onSelectionChange={setSelected}
>
<Dropdown.Item id="bold" textValue="Bold">
<Label>Bold</Label>
<Dropdown.ItemIndicator />
</Dropdown.Item>
<Dropdown.Item id="italic" textValue="Italic">
<Label>Italic</Label>
<Dropdown.ItemIndicator />
</Dropdown.Item>
<Dropdown.Item id="underline" textValue="Underline">
<Label>Underline</Label>
<Dropdown.ItemIndicator />
</Dropdown.Item>
</Dropdown.Menu>
</Dropdown.Popover>
</Dropdown>
</div>
)
}Controlled Open State
Dropdown is: closed
import { Button, Dropdown, Label } from "heroui-solid"
import { createSignal } from "solid-js"
export function ControlledOpenState() {
const [open, setOpen] = createSignal(false)
return (
<div class="flex min-w-sm flex-col items-center justify-center gap-4">
<p class="text-sm text-muted">
Dropdown is: <strong>{open() ? "open" : "closed"}</strong>
</p>
<Dropdown isOpen={open()} onOpenChange={setOpen}>
<Button aria-label="Menu" variant="secondary">
Actions
</Button>
<Dropdown.Popover>
<Dropdown.Menu>
<Dropdown.Item id="new-file" textValue="New file">
<Label>New file</Label>
</Dropdown.Item>
<Dropdown.Item id="open-file" textValue="Open file">
<Label>Open file</Label>
</Dropdown.Item>
<Dropdown.Item id="save-file" textValue="Save file">
<Label>Save file</Label>
</Dropdown.Item>
<Dropdown.Item
id="delete-file"
textValue="Delete file"
variant="danger"
>
<Label>Delete file</Label>
</Dropdown.Item>
</Dropdown.Menu>
</Dropdown.Popover>
</Dropdown>
</div>
)
}Custom Trigger
import { ArrowRightFromSquare, Gear, Persons } from "gravity-icons-solid"
import { Avatar, Dropdown, Label } from "heroui-solid"
export function CustomTrigger() {
return (
<Dropdown>
<Dropdown.Trigger class="rounded-full">
<Avatar>
<Avatar.Image
alt="Junior Garcia"
src="https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/orange.jpg"
/>
<Avatar.Fallback delayMs={600}>JD</Avatar.Fallback>
</Avatar>
</Dropdown.Trigger>
<Dropdown.Popover>
<div class="px-3 pt-3 pb-1">
<div class="flex items-center gap-2">
<Avatar size="sm">
<Avatar.Image
alt="Jane"
src="https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/orange.jpg"
/>
<Avatar.Fallback delayMs={600}>JD</Avatar.Fallback>
</Avatar>
<div class="flex flex-col gap-0">
<p class="text-sm leading-5 font-medium">Jane Doe</p>
<p class="text-xs leading-none text-muted">jane@example.com</p>
</div>
</div>
</div>
<Dropdown.Menu>
<Dropdown.Item id="dashboard" textValue="Dashboard">
<Label>Dashboard</Label>
</Dropdown.Item>
<Dropdown.Item id="profile" textValue="Profile">
<Label>Profile</Label>
</Dropdown.Item>
<Dropdown.Item id="settings" textValue="Settings">
<div class="flex w-full items-center justify-between gap-2">
<Label>Settings</Label>
<Gear class="size-3.5 text-muted" />
</div>
</Dropdown.Item>
<Dropdown.Item id="new-project" textValue="New project">
<div class="flex w-full items-center justify-between gap-2">
<Label>Create Team</Label>
<Persons class="size-3.5 text-muted" />
</div>
</Dropdown.Item>
<Dropdown.Item id="logout" textValue="Logout" variant="danger">
<div class="flex w-full items-center justify-between gap-2">
<Label>Log Out</Label>
<ArrowRightFromSquare class="size-3.5 text-danger" />
</div>
</Dropdown.Item>
</Dropdown.Menu>
</Dropdown.Popover>
</Dropdown>
)
}Related Components
- Select: Collapsible list of options with selection
- Button: Renders as the trigger when placed directly inside
Dropdown - Label / Description: Item content slots
Styling
Passing Tailwind CSS classes
import { Dropdown } from "heroui-solid";
function CustomDropdown() {
return (
<Dropdown>
<Dropdown.Trigger class="rounded-lg border p-2 bg-surface">
Actions
</Dropdown.Trigger>
<Dropdown.Popover class="min-w-[200px]">
<Dropdown.Menu>
<Dropdown.Item id="item-1" textValue="Item 1" class="hover:bg-surface-secondary">
Item 1
</Dropdown.Item>
</Dropdown.Menu>
</Dropdown.Popover>
</Dropdown>
);
}Customizing the component classes
To customize the Dropdown component classes, you can use the @layer components directive.
Learn more.
@layer components {
.dropdown {
@apply flex flex-col gap-1;
}
.dropdown__trigger {
@apply outline-none;
}
.dropdown__popover {
@apply rounded-lg border border-border bg-overlay p-2;
}
.dropdown__menu {
@apply flex flex-col gap-1;
}
}HeroUI follows the BEM methodology to ensure component variants and states are reusable and easy to customize.
CSS Classes
The Dropdown component uses these CSS classes (View source styles):
Base Classes
.dropdown- Base dropdown container.dropdown__trigger- The button or element that triggers the dropdown.dropdown__popover- The popover container.dropdown__menu- The menu container inside the popover
State Classes
.dropdown__trigger:focus-visible- Focused trigger state.dropdown__trigger:disabled- Disabled trigger state.dropdown__trigger:active- Pressed trigger state.dropdown__popover[data-expanded]- Entering animation state (Kobalte attribute).dropdown__popover[data-closed]- Exiting animation state (Kobalte attribute).dropdown__menu[data-selection-mode="single"]- Single selection mode.dropdown__menu[data-selection-mode="multiple"]- Multiple selection mode
Menu Component Classes
The Dropdown component uses Menu, MenuItem, and MenuSection as base components. These classes are also available for customization:
Menu Classes
.menu- Base menu container (menu.css)[data-slot="separator"]- Separator elements within the menu
MenuItem Classes
.menu-item- Base menu item container (menu-item.css).menu-item__indicator- Selection indicator (checkmark or dot)[data-slot="menu-item-indicator--checkmark"]- Checkmark indicator SVG[data-slot="menu-item-indicator--dot"]- Dot indicator SVG
.menu-item__indicator--submenu- Submenu indicator (chevron).menu-item--default- Default variant styling.menu-item--danger- Danger variant styling
MenuItem State Classes
.menu-item[data-highlighted]- Highlighted item state (Kobalte attribute; hover and keyboard focus).menu-item[data-selected="true"]- Selected item state.menu-item[aria-checked="true"]- Checked item (ARIA).menu-item[data-disabled]- Disabled item state.menu-item[data-has-submenu="true"]- Item with submenu.menu-item[data-selection-mode="single"]- Single selection mode.menu-item[data-selection-mode="multiple"]- Multiple selection mode
MenuSection Classes
.menu-section- Base menu section container (menu-section.css)
Interactive States
The component supports both CSS pseudo-classes and data attributes for flexibility:
- Hover / highlighted item:
:hoveror[data-highlighted](Kobalte attribute) - Focus:
:focus-visibleon the trigger and items - Pressed:
:active(scale transform) - Disabled:
[data-disabled]on items,:disabledon the trigger - Selected:
[data-selected="true"]or[aria-checked="true"]on items - Open animations:
[data-expanded]/[data-closed]on the popover (Kobalte attributes)
API Reference
Dropdown Props
| Prop | Type | Default | Description |
|---|---|---|---|
isOpen | boolean | - | Sets the open state of the menu (controlled) |
defaultOpen | boolean | - | Sets the default open state of the menu (uncontrolled) |
onOpenChange | (isOpen: boolean) => void | - | Handler called when the open state changes |
trigger | "press" | "longPress" | "press" | The type of interaction that triggers the menu |
children | JSX.Element | - | Dropdown content |
Dropdown.Trigger Props
| Prop | Type | Default | Description |
|---|---|---|---|
class | string | - | Additional CSS classes (e.g. buttonVariants({...})) |
children | JSX.Element | - | Trigger content |
A Button placed directly inside Dropdown renders as the trigger automatically.
Dropdown.Popover Props
| Prop | Type | Default | Description |
|---|---|---|---|
placement | string | "bottom" | Placement relative to the trigger (Kobalte placements, e.g. "top") |
class | string | - | Additional CSS classes |
children | JSX.Element | - | Content children |
Dropdown.Menu Props
| Prop | Type | Default | Description |
|---|---|---|---|
selectionMode | "single" | "multiple" | "none" | "none" | Whether single or multiple selection is enabled |
selectedKeys | Selection | - | The currently selected keys (controlled) |
defaultSelectedKeys | Selection | - | The initial selected keys (uncontrolled) |
onSelectionChange | (keys: Selection) => void | - | Handler called when the selection changes |
disabledKeys | Iterable<string> | - | Keys of disabled items |
onAction | (key: string) => void | - | Handler called when an item is activated |
class | string | - | Additional CSS classes |
children | JSX.Element | - | Menu content |
Dropdown.Section Props
| Prop | Type | Default | Description |
|---|---|---|---|
selectionMode | "single" | "multiple" | - | Selection mode for items within this section |
selectedKeys | Selection | - | The currently selected keys (controlled) |
defaultSelectedKeys | Selection | - | The initial selected keys (uncontrolled) |
onSelectionChange | (keys: Selection) => void | - | Handler called when the selection changes |
disabledKeys | Iterable<string> | - | Keys of disabled items |
class | string | - | Additional CSS classes |
children | JSX.Element | - | Section content |
Dropdown.Item Props
| Prop | Type | Default | Description |
|---|---|---|---|
id | string | - | Unique identifier for the item |
textValue | string | - | Text content of the item for typeahead |
isDisabled | boolean | false | Whether the item is disabled |
onAction | () => void | - | Item-level activation handler |
variant | "default" | "danger" | "default" | Visual variant of the item |
class | string | - | Additional CSS classes |
children | JSX.Element | - | Item content |
Dropdown.ItemIndicator Props
| Prop | Type | Default | Description |
|---|---|---|---|
type | "checkmark" | "dot" | "checkmark" | Type of indicator to display |
class | string | - | Additional CSS classes |
children | JSX.Element | (state) => JSX.Element | - | Custom indicator content or render function |
When using a render function, these values are provided:
| Prop | Type | Description |
|---|---|---|
isSelected | boolean | Whether the item is selected |
Dropdown.SubmenuIndicator Props
| Prop | Type | Default | Description |
|---|---|---|---|
class | string | - | Additional CSS classes |
children | JSX.Element | - | Custom indicator content |
Dropdown.SubmenuTrigger Props
| Prop | Type | Default | Description |
|---|---|---|---|
children | JSX.Element | - | Submenu trigger content (a Dropdown.Item and a Dropdown.Popover) |
Examples
Basic Usage
import { Button, Dropdown, Label } from "heroui-solid";
<Dropdown>
<Button aria-label="Menu" variant="secondary">
Actions
</Button>
<Dropdown.Popover>
<Dropdown.Menu onAction={(key) => alert(`Selected: ${key}`)}>
<Dropdown.Item id="new-file" textValue="New file">
<Label>New file</Label>
</Dropdown.Item>
<Dropdown.Item id="open-file" textValue="Open file">
<Label>Open file</Label>
</Dropdown.Item>
<Dropdown.Item id="delete-file" textValue="Delete file" variant="danger">
<Label>Delete file</Label>
</Dropdown.Item>
</Dropdown.Menu>
</Dropdown.Popover>
</Dropdown>With Sections
import { Button, Dropdown, Header, Label, Separator } from "heroui-solid";
<Dropdown>
<Button aria-label="Menu" variant="secondary">
Actions
</Button>
<Dropdown.Popover>
<Dropdown.Menu onAction={(key) => alert(`Selected: ${key}`)}>
<Dropdown.Section>
<Header>Actions</Header>
<Dropdown.Item id="new-file" textValue="New file">
<Label>New file</Label>
</Dropdown.Item>
<Dropdown.Item id="edit-file" textValue="Edit file">
<Label>Edit file</Label>
</Dropdown.Item>
</Dropdown.Section>
<Separator />
<Dropdown.Section>
<Header>Danger zone</Header>
<Dropdown.Item id="delete-file" textValue="Delete file" variant="danger">
<Label>Delete file</Label>
</Dropdown.Item>
</Dropdown.Section>
</Dropdown.Menu>
</Dropdown.Popover>
</Dropdown>Controlled Selection
import { Button, Dropdown, Label, type Selection } from "heroui-solid";
import { createSignal } from "solid-js";
function ControlledDropdown() {
const [selected, setSelected] = createSignal<Selection>(new Set(["bold"]));
return (
<Dropdown>
<Button aria-label="Menu" variant="secondary">
Actions
</Button>
<Dropdown.Popover>
<Dropdown.Menu
selectedKeys={selected()}
selectionMode="multiple"
onSelectionChange={setSelected}
>
<Dropdown.Item id="bold" textValue="Bold">
<Label>Bold</Label>
<Dropdown.ItemIndicator />
</Dropdown.Item>
<Dropdown.Item id="italic" textValue="Italic">
<Label>Italic</Label>
<Dropdown.ItemIndicator />
</Dropdown.Item>
</Dropdown.Menu>
</Dropdown.Popover>
</Dropdown>
);
}With Submenus
import { Button, Dropdown, Label } from "heroui-solid";
<Dropdown>
<Button aria-label="Menu" variant="secondary">
Share
</Button>
<Dropdown.Popover>
<Dropdown.Menu onAction={(key) => alert(`Selected: ${key}`)}>
<Dropdown.Item id="copy-link" textValue="Copy Link">
<Label>Copy Link</Label>
</Dropdown.Item>
<Dropdown.SubmenuTrigger>
<Dropdown.Item id="share" textValue="Share">
<Label>Other</Label>
<Dropdown.SubmenuIndicator />
</Dropdown.Item>
<Dropdown.Popover>
<Dropdown.Menu>
<Dropdown.Item id="whatsapp" textValue="WhatsApp">
<Label>WhatsApp</Label>
</Dropdown.Item>
<Dropdown.Item id="telegram" textValue="Telegram">
<Label>Telegram</Label>
</Dropdown.Item>
</Dropdown.Menu>
</Dropdown.Popover>
</Dropdown.SubmenuTrigger>
</Dropdown.Menu>
</Dropdown.Popover>
</Dropdown>Accessibility
The Dropdown component implements the ARIA menu pattern and provides:
- Full keyboard navigation support (arrow keys, home/end, typeahead)
- Screen reader announcements for actions and selection changes
- Proper focus management
- Support for disabled states
- Long press interaction support
- Submenu navigation
Differences from HeroUI React
- Either place a
Buttondirectly insideDropdown(it renders as the trigger, mirroring upstream), or use an explicitDropdown.Triggerstyled withbuttonVariants— Kobalte's trigger is its own button, so a nestedButtoninsideDropdown.Triggerwould nest buttons. - Selection is managed by the port over Kobalte items:
Selectionis"all" | Set<string>, andonSelectionChangealways receives aSet. - The long-press trigger opens after a 500ms hold or with Alt+ArrowDown/ArrowUp; press and keyboard activation do not open it, mirroring React Aria.
- Item keys come from our
idprop and reachonAction; Kobalte generates its own internal collection keys. - Popover placements use Kobalte's syntax (
"top","bottom-start", …) and enter/exit animations ridedata-expanded/data-closed. Submenu popovers are always placed to the side (right-start).
Last updated: 7/19/26, 3:27 AM