# Dropdown

> A menu on the native Popover API — the trigger opens a list of DropdownItems in the top layer, with sections and submenus.

URL: https://apogee.avaui.dev/docs/dropdown
Last updated: 2026-10-15

A menu on the native Popover API — the trigger opens a list of DropdownItems in the top layer, with sections and submenus.

Last updated October 15, 2026

Uses JavaScript

Adds the menu keyboard pattern (ArrowDown on the trigger opens and focuses the first item; arrows, Home and End move; ArrowRight / ArrowLeft open and close submenus), closes the whole menu when an item is activated, opens submenus on hover, and dispatches `apogee:dropdown-open` / `-close`.

**Without JS:** The menu opens and closes natively (popovertarget), including Escape and outside click. Without JS there is no arrow-key navigation, items don't close the menu on activation, and submenus open on click only.

```astro
<Dropdown label="Options">
<DropdownItem href="#">Edit</DropdownItem>
<DropdownItem href="#">Duplicate</DropdownItem>
<DropdownItem href="#">Delete</DropdownItem>
</Dropdown>
```

## Add

```sh
npx @ava-ui/apogee add dropdown
```

Uses `@lucide/astro` (`ChevronDown`, `ChevronRight`) and `utils`. Ships `DropdownItem.astro`, `DropdownSection.astro` and `DropdownSub.astro` in the same folder.

## Usage

```astro
---
import Dropdown from '@/components/apogee/Dropdown/Dropdown.astro'
import DropdownItem from '@/components/apogee/Dropdown/DropdownItem.astro'
---
<Dropdown label="Options" placement="bottom-end">
  <DropdownItem href="/edit">Edit</DropdownItem>
  <DropdownItem color="critical">Delete</DropdownItem>
</Dropdown>
```

## Anatomy

-   **Trigger** — a `<button popovertarget>` showing `label` and a chevron that rotates while open. Or your own element in the `anchor` slot: give it `popovertarget` (the menu `id`) and `aria-haspopup="menu"`.
-   **Menu** — the `role="menu"` popover, anchored to the trigger; default slot. Fill it with `DropdownItem`s (`role="menuitem"`), `DropdownSection`s (`role="group"`) and `DropdownSub`s (an item that opens a nested menu).

## API

`@composable` — exposed to Content Editors in AVA. Any other attribute passes through to the root `<div>`.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `label` | `string` | — | Trigger text and the menu’s accessible name. |
| `id` | `string` | auto-generated | Menu id. Set it when an `anchor` element needs `popovertarget`. |
| `placement` | `'top' \| 'top-start' \| 'top-end' \| 'bottom' \| 'bottom-start' \| 'bottom-end' \| 'start' \| 'start-top' \| 'start-bottom' \| 'end' \| 'end-top' \| 'end-bottom'` | `'bottom-start'` | Side and alignment; flips when out of room. See [Popover](https://apogee.avaui.dev/docs/popover#placement) for all twelve. |
| `width` | `'trigger' \| string` | — | `'trigger'` matches the trigger’s width; otherwise any CSS length. |
| `maxHeight` | `string` | — | Caps the menu height; items scroll past it. |
| `class` | `string` | — | Extra classes on the root `<div>`, merged last. |

### `DropdownItem` props

A link when `href` is set, otherwise a `<button>`. `@composable`. Slots: `start` (icon), default (label), `end` (shortcut or badge).

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `href` | `string` | — | Renders an `<a>` instead of a `<button>`. |
| `color` | `'neutral' \| 'primary' \| 'critical'` | `'neutral'` | Text and hover tint. |
| `size` | `'small' \| 'medium' \| 'large'` | `'medium'` | Padding, text and icon size. |
| `selected` | `boolean` | `false` | The chosen item: `aria-current="true"` and a faded fill (`aria-checked` with a checkbox or radio indicator). |
| `indicator` | `'none' \| 'check' \| 'checkbox' \| 'radio'` | `'none'` | A leading selection mark. The column stays reserved when unselected so labels align; `checkbox` / `radio` switch the role to `menuitemcheckbox` / `menuitemradio`. |
| `disabled` | `boolean` | `false` | Native `disabled` on a button; `aria-disabled` + `tabindex="-1"` on a link. |
| `class` | `string` | — | Extra classes, merged last. |

### `DropdownSection` props

A `role="group"` with an optional label and a rule above it when it is not first.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `label` | `string` | — | Group heading (`aria-labelledby`). |

### `DropdownSub` props

An item that opens a nested menu beside it. Slots: `start` (icon), default (items).

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `label` | `string` | — | Item text and the nested menu’s accessible name. |
| `size` | `'small' \| 'medium' \| 'large'` | `'medium'` | Matches the items around it. |
| `placement` | placement | `'end-top'` | Where the nested menu opens. |

## Placement

```astro
<Dropdown label="bottom-start" placement="bottom-start">…</Dropdown>
<Dropdown label="bottom-end" placement="bottom-end">…</Dropdown>
<Dropdown label="top-start" placement="top-start">…</Dropdown>
<Dropdown label="end-top" placement="end-top">…</Dropdown>
```

## Custom trigger

```astro
<Dropdown id="dd-actions" label="Actions">
<Button slot="anchor" popovertarget="dd-actions" aria-haspopup="menu" variant="outline" color="primary">Actions</Button>
<DropdownItem href="#">Edit</DropdownItem>
<DropdownItem href="#">Delete</DropdownItem>
</Dropdown>
```

## Sizes

```astro
<DropdownItem size="small">…</DropdownItem>
<DropdownItem size="medium">…</DropdownItem>
<DropdownItem size="large">…</DropdownItem>
```

## Colours

```astro
<DropdownItem color="neutral">Rename</DropdownItem>
<DropdownItem color="primary">Make default</DropdownItem>
<DropdownItem color="critical">Delete</DropdownItem>
```

## Selected and disabled

```astro
<DropdownItem selected>Grid</DropdownItem>
<DropdownItem>List</DropdownItem>
<DropdownItem disabled>Board (soon)</DropdownItem>
<DropdownItem href="#" disabled>Timeline (soon)</DropdownItem>
```

## Indicators

Every `indicator`:

```astro
<Dropdown label="View">
<DropdownItem indicator="check" selected>Comfortable</DropdownItem>
<DropdownItem indicator="check">Compact</DropdownItem>
<DropdownSection label="Show">
  <DropdownItem indicator="checkbox" selected>Line numbers</DropdownItem>
  <DropdownItem indicator="checkbox">Minimap</DropdownItem>
</DropdownSection>
<DropdownSection label="Wrap">
  <DropdownItem indicator="radio" selected>Soft</DropdownItem>
  <DropdownItem indicator="radio">Off</DropdownItem>
</DropdownSection>
</Dropdown>
```

The mark reflects the server-rendered `selected`; the item is a plain button or link, so your own click handler updates state and re-renders, as with any menu.

## Sections and end slot

```astro
<Dropdown label="File">
<DropdownSection label="Edit">
  <DropdownItem><Pencil slot="start" /> Rename <Kbd slot="end">F2</Kbd></DropdownItem>
  <DropdownItem><Copy slot="start" /> Duplicate <Kbd slot="end">⌘D</Kbd></DropdownItem>
</DropdownSection>
<DropdownSection label="Danger">
  <DropdownItem color="critical"><Trash2 slot="start" /> Delete <Kbd slot="end">⌫</Kbd></DropdownItem>
</DropdownSection>
</Dropdown>
```

## Submenu

Hover, click or `ArrowRight` opens the nested menu; `ArrowLeft` or `Escape` closes it. Picking an item closes the whole chain.

```astro
<Dropdown label="Options">
<DropdownItem><Pencil slot="start" /> Edit</DropdownItem>
<DropdownSub label="Share">
  <Share2 slot="start" />
  <DropdownItem><Link slot="start" /> Copy link</DropdownItem>
  <DropdownItem><Mail slot="start" /> Email</DropdownItem>
</DropdownSub>
<DropdownItem color="critical"><Trash2 slot="start" /> Delete</DropdownItem>
</Dropdown>
```

## Width and max height

```astro
<Dropdown label="Pick a month (matches the trigger)" width="trigger" maxHeight="10rem">
…twelve items…
</Dropdown>
```

## States

-   **Closed / open** — the native popover toggle; the chevron rotates 180° while open.
-   **Out of room** — the menu flips to the opposite side, no JS.
-   **Hover / focus** — the trigger tints on hover; keyboard focus shows a visible ring. Items tint on hover and on keyboard focus.
-   **No JS** — see the JsNote above.

## Events

The root `<div>` dispatches `apogee:dropdown-open` and `apogee:dropdown-close` (bubbling) alongside the native `toggle` event on the menu. Activating an item closes the menu, nested menus included.

## Examples

A user menu: a custom trigger, sections, a shortcut in the end slot and a critical last item:

```astro
<Dropdown id="dd-user" label="Account" placement="bottom-end">
<Button slot="anchor" popovertarget="dd-user" aria-haspopup="menu" variant="ghost" color="neutral">Ada Lovelace</Button>
<DropdownSection label="ada@example.com">
  <DropdownItem href="#"><User slot="start" /> Profile</DropdownItem>
  <DropdownItem href="#"><Settings slot="start" /> Settings <Kbd slot="end">⌘,</Kbd></DropdownItem>
</DropdownSection>
<DropdownSection>
  <DropdownItem color="critical"><LogOut slot="start" /> Sign out</DropdownItem>
</DropdownSection>
</Dropdown>
```

## Accessibility

| Key | Action |
| --- | --- |
| `Enter` `Space` | On the trigger, toggle the menu; on an item, activate it and close the menu. |
| `↓` | On the closed trigger, opens the menu and focuses the first item. |
| `↓` `↑` | Move between items, wrapping at the ends. |
| `Home` `End` | First / last item. |
| `→` | On a submenu item, opens the submenu and focuses its first item (`←` under `dir="rtl"`). |
| `←` | Inside a submenu, closes it and returns focus to its item (`→` under `dir="rtl"`). |
| `Esc` | Closes the open menu or submenu and returns focus to its trigger. |

-   The menu has `role="menu"` and takes `label` as its accessible name; `DropdownItem`s carry `role="menuitem"`; sections are `role="group"`.
-   The trigger is a `<button popovertarget>` with `aria-haspopup="menu"`; the browser sets `aria-expanded`. Clicking outside closes the menu (native).
-   A disabled link item keeps its text but is `aria-disabled` and out of the tab order.
-   **No JS** — the menu still opens, closes and light-dismisses; items are reachable with `Tab` in document order.

## Tokens

| Token | Used for |
| --- | --- |
| `--border-neutral` | Trigger + menu border, section rules |
| `--background-page` | Trigger background |
| `--background-neutral-faded-highlighted` | Trigger and neutral item hover |
| `--background-neutral-faded` | Selected neutral item |
| `--background-primary-faded` / `--background-critical-faded` | Primary / critical item hover and selected |
| `--foreground-neutral` / `-primary` / `-critical` | Item text by colour |
| `--foreground-neutral-faded` | Chevrons, icons, end slot, section labels |
| `--foreground-disabled` | Disabled items |
| `--background-elevation-overlay` | Menu background |
| `--outline-color-focus` | Trigger focus ring |
| `--duration-fast` / `--ease-standard` | Menu open fade/scale |

## Do / Don’t

-   **Do** keep the trigger `label` short — it’s the only cue for what the menu holds.
-   **Do** order items by frequency of use, with destructive actions last, in their own section and `color="critical"`.
-   **Don’t** pack more than a handful of items in one menu — use a full page or `Combobox` / `CommandPalette` for long lists.
-   **Don’t** nest more than one level of `DropdownSub`.

## Content

-   Item labels are short verbs or verb phrases (“Edit”, “Duplicate”, “Delete”), sentence case, no ending punctuation.
-   The trigger `label` names what the menu contains, not a bare “Menu” or “Options” when a more specific noun fits.

## Related

[

ContextMenuA styleable right-click menu — a native popover at the cursor over a target; closes on outside click, Escape, or scroll.

](https://apogee.avaui.dev/docs/context-menu)[

MenubarA desktop-style app menubar (File, Edit, View…) with keyboard navigation between menus.

](https://apogee.avaui.dev/docs/menubar)[

PopoverRich content on click, built on the native Popover API — top layer and light dismiss for free, anchored with CSS anchor positioning.

](https://apogee.avaui.dev/docs/popover)[

SelectA labeled, native <select> — full keyboard and mobile picker support for free — or a custom listbox with rich options over the same form control.

](https://apogee.avaui.dev/docs/select)
