Skip to main content

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>
  )
}
  • 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

The Dropdown component uses Menu, MenuItem, and MenuSection as base components. These classes are also available for customization:

  • .menu - Base menu container (menu.css)
    • [data-slot="separator"] - Separator elements within the menu
  • .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
  • .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

Interactive States

The component supports both CSS pseudo-classes and data attributes for flexibility:

  • Hover / highlighted item: :hover or [data-highlighted] (Kobalte attribute)
  • Focus: :focus-visible on the trigger and items
  • Pressed: :active (scale transform)
  • Disabled: [data-disabled] on items, :disabled on 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

PropTypeDefaultDescription
isOpenboolean-Sets the open state of the menu (controlled)
defaultOpenboolean-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
childrenJSX.Element-Dropdown content
PropTypeDefaultDescription
classstring-Additional CSS classes (e.g. buttonVariants({...}))
childrenJSX.Element-Trigger content

A Button placed directly inside Dropdown renders as the trigger automatically.

PropTypeDefaultDescription
placementstring"bottom"Placement relative to the trigger (Kobalte placements, e.g. "top")
classstring-Additional CSS classes
childrenJSX.Element-Content children
PropTypeDefaultDescription
selectionMode"single" | "multiple" | "none""none"Whether single or multiple selection is enabled
selectedKeysSelection-The currently selected keys (controlled)
defaultSelectedKeysSelection-The initial selected keys (uncontrolled)
onSelectionChange(keys: Selection) => void-Handler called when the selection changes
disabledKeysIterable<string>-Keys of disabled items
onAction(key: string) => void-Handler called when an item is activated
classstring-Additional CSS classes
childrenJSX.Element-Menu content
PropTypeDefaultDescription
selectionMode"single" | "multiple"-Selection mode for items within this section
selectedKeysSelection-The currently selected keys (controlled)
defaultSelectedKeysSelection-The initial selected keys (uncontrolled)
onSelectionChange(keys: Selection) => void-Handler called when the selection changes
disabledKeysIterable<string>-Keys of disabled items
classstring-Additional CSS classes
childrenJSX.Element-Section content
PropTypeDefaultDescription
idstring-Unique identifier for the item
textValuestring-Text content of the item for typeahead
isDisabledbooleanfalseWhether the item is disabled
onAction() => void-Item-level activation handler
variant"default" | "danger""default"Visual variant of the item
classstring-Additional CSS classes
childrenJSX.Element-Item content
PropTypeDefaultDescription
type"checkmark" | "dot""checkmark"Type of indicator to display
classstring-Additional CSS classes
childrenJSX.Element | (state) => JSX.Element-Custom indicator content or render function

When using a render function, these values are provided:

PropTypeDescription
isSelectedbooleanWhether the item is selected
PropTypeDefaultDescription
classstring-Additional CSS classes
childrenJSX.Element-Custom indicator content
PropTypeDefaultDescription
childrenJSX.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 Button directly inside Dropdown (it renders as the trigger, mirroring upstream), or use an explicit Dropdown.Trigger styled with buttonVariants — Kobalte's trigger is its own button, so a nested Button inside Dropdown.Trigger would nest buttons.
  • Selection is managed by the port over Kobalte items: Selection is "all" | Set<string>, and onSelectionChange always receives a Set.
  • 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 id prop and reach onAction; Kobalte generates its own internal collection keys.
  • Popover placements use Kobalte's syntax ("top", "bottom-start", …) and enter/exit animations ride data-expanded/data-closed. Submenu popovers are always placed to the side (right-start).

Last updated: 7/19/26, 3:27 AM

HeroUI SolidUnofficial SolidJS port of HeroUI v3, built on Kobalte and @heroui/styles