Skip to content

Dropdown Menu

ClassPart
.dropdown-menuThe popover panel
.dropdown-menu-itemAn action
.dropdown-menu-labelGroup heading — names the menu
.dropdown-menu-separatorDivider
.dropdown-menu-shortcutRight-aligned key hint
.dropdown-menu-groupGroups related items under one role
.dropdown-menu-checkbox-itemItem that toggles
.dropdown-menu-radio-groupContainer for mutually exclusive items
.dropdown-menu-radio-itemOne choice within a radio group
.dropdown-menu-item-indicatorSlot for the check or dot
.dropdown-menu-sub-triggerItem that opens a nested menu
AttributeOnMeaning
data-pk-placementthe panelbottom-start (default), bottom-end, top-start, top-end
data-pk-offsetthe panelGap from the trigger in pixels. Defaults to 4
data-pk-anchorthe panelSelector to position against, when the trigger is not the popovertarget button
data-pk-keep-openan itemActivating it does not close the menu
data-variant="destructive"an itemDanger styling
data-insetan item or labelAligns with items that have icons
data-disabledan itemMirrored to aria-disabled, skipped by arrow keys
data-state="open" | "closed"panel and triggerSet by the runtime

The panel flips above the trigger when there is not enough room below, and is clamped to stay inside the viewport.

.dropdown-menu-shortcut pushes a key hint to the trailing edge. Put the keys inside it as Kbd elements — one per key — and add kbd-group so they sit together. It is a hint, not a binding: register the actual shortcut yourself.

<span class="dropdown-menu-shortcut kbd-group">
<kbd class="kbd"></kbd><kbd class="kbd">S</kbd>
</span>

Give every item an icon, or use data-inset on the ones without so the labels still line up.

Each item keeps its own state in aria-checked, and pk:menu:change fires with { checked, value } where value comes from data-value. Pair them with data-pk-keep-open so several can be toggled in one visit.

Selecting one clears its siblings in the same .dropdown-menu-radio-group, and leaves checkbox items elsewhere in the menu alone.

A .dropdown-menu-sub-trigger points popovertarget at a nested .dropdown-menu. Put the submenu inside its parent in the DOM — that containment is what makes the browser treat them as parent and child, so opening the submenu does not dismiss the menu it came from.

opens a submenu and focuses its first item; closes it and returns to the trigger. Moving the pointer to a different item closes it too.

An item can carry both: the indicator on the trailing edge and its own icon inline.

data-variant="destructive" recolours the item and its icon, and gives it a destructive surface when focused.

Labels, groups, shortcuts, a submenu, a disabled item and a destructive action in one menu.

Set dir="rtl" on the page or on any subtree — padding, corners and positions follow the reading direction on their own. See the button page for a live example.

An item paints on :focus, not :hover — and the pointer moves focus rather than lighting a separate state. That is what Radix does inside shadcn/ui, and it means keyboard and pointer share one highlight instead of two that can disagree.

The highlight is --accent, which is deliberately quiet: 3% grey on white, and one step up from the panel in dark. It has to differ from --popover or a menu has no visible hover at all — the palette test checks exactly that pair.

KeyAction
/ Move between enabled items, wrapping at the ends
Home / EndFirst / last enabled item
a–z, 0–9Type-ahead to the first item starting with what you type
Open the submenu under the cursor
Close a submenu and return to its trigger
Esc / TabClose and return focus to the trigger

Items are real <button> elements, so Enter and Space activate them without extra code. Disabled items keep aria-disabled="true" rather than disabled, staying announced but skipped.