Dropdown
A click-triggered menu that opens anchored to the trigger element.
Basic usage
Trigger on click
Trigger
The trigger attribute controls how the menu opens: click (default) / hover / focus, multi-selectable with spaces (e.g. "click hover"). With hover, hover-delay / hover-hide-delay debounce open/close (default 150 / 100ms — without a delay hover flickers). The hover area is the trigger plus the floating panel (moving across the gap does not close it).
Hover trigger
Focus trigger
Multiple triggers (click + hover)
Custom hover delays
Touchscreen degradation (automatic on coarse pointers)
Touch devices (pointer: coarse, e.g. phones and tablets) have no stable hover semantics: a tap synthesizes both hover and click events, which would open-then-immediately-close the menu with the desktop behavior. When trigger includes hover, the hover channel is automatically disabled on coarse pointers and degrades to tap toggling — tap the trigger to open, tap again to close, or tap outside the menu to dismiss (outside dismiss replaces hover-out). Fine-pointer (desktop mouse) behavior is unchanged; compatible with controlled open.
Tap toggling on touch (automatic on touch devices)
Placement
Four directions
12-way placement
placement supports 12 directions: each of the four bases (top / bottom / left / right) pairs with a cross-axis -start / -end align (bottom-start — the panel's left edge aligns with the trigger's left edge — is the most common form). When the panel flips along the main axis due to lack of space, the align suffix is preserved (bottom-start → top-start), and the aligned position is still clamped to the viewport.
12-way placement
Nested submenus
items entries support a children array for cascading submenus (any depth); hover/click expands, selecting a leaf collapses and closes. The floating menu reuses oas-menu, so nested submenus automatically flip left/up near viewport edges to stay fully visible.
Nested submenus
Disabled items
Disabled items
Disabled dropdown
disabled disables the whole dropdown: click / hover / focus triggers are ignored, the split arrow button is disabled, the host is desaturated (opacity .6) and syncs aria-disabled.
Disabled dropdown
Arrow
The menu panel shows an arrow pointing at the trigger element . The arrow attribute toggles it (shown by default; arrow="false" hides it). arrow-point-at-center pins the arrow to the panel center (by default the arrow follows the trigger's projection, so it still points at the trigger even after viewport-avoidance shifting). auto-adjust-overflow controls whether the placement auto-flips when there is not enough viewport space (enabled by default).
With arrow
Arrow visibility
Arrow at center & no overflow adjust
Selection event
Selection event
Keep open after select
hide-on-click controls whether selecting a menu item closes the menu (default true — close on select; "false" keeps it open, useful for multi-select / check scenarios).
Keep open after select
Controlled display
The open attribute is controlled: an external button can set/remove open to show/hide the menu (clicking outside / pressing Esc / selecting an item still closes it).
Controlled display (open attribute)
Open-change event
The component fires oas-open-change (detail: { open }) whenever it opens or closes itself (click / hover / focus / Esc / outside click / select) — the host can sync state in controlled mode (same semantics as tooltip / popover; externally writing open also triggers it).
oas-open-change event
Offset & scroll
offset adjusts the gap between the panel and the trigger (default 8px). When the page scrolls, the panel repositions to follow the trigger (fixing the fixed-positioning detachment); close-on-scroll closes it on scroll instead.
Offset
Close on scroll
Controlled selection
The value attribute is controlled: an external value sets the selected item (the dropdown shows no check mark, so a tag echoes the current value in real time); selecting a menu item also updates value and fires oas-select.
Controlled selection (value attribute)
Split button
The split attribute turns the dropdown into a split button : a main button plus a separate arrow button. Click the arrow to open the menu; clicking the main button fires oas-action (bind your primary action, e.g. save); selecting a menu item still fires oas-select.
Split button
Main button action event
Loading menu items
A menu item with loading: true enters a loading state: it shows a spinning indicator and is not selectable (click / keyboard / hover are all ignored); update items to remove loading once the async work finishes. The demo below marks "Save" as loading for ~1.5s after you select it.
Loading menu items
Async operation demo
Declarative child-element channel
Besides the items JSON, you can declare the menu with <oas-dropdown-item> / <oas-dropdown-group> / <oas-dropdown-divider> child elements (the items attribute wins when set explicitly; otherwise children are parsed and converge to the same rendering path). Default slot text becomes the label; attributes map to the items fields: value / disabled / loading / icon / kind / danger / href / target / rel. Nested elements inside an <oas-dropdown-item> become cascading submenus recursively; an <oas-dropdown-group>'s label is the group title (value can act as a radio-group id) and its children flatten to the same level. Adding/removing children or changing their attributes/text re-renders automatically (MutationObserver).
Declarative children (group / divider / nested / checkbox / danger / href)
Dynamic add/remove (MutationObserver auto refresh)
Leading media (slot="leading")
Menu items support a leading media slot: put a <template slot="leading"> (or a direct child with slot="leading") inside an <oas-dropdown-item> (shared with <oas-menu-item>); the content renders before the label and takes precedence over the icon attribute (when both are given the icon is not rendered); in the loading state the spinner replaces the leading media. Suited to avatars, color chips, and any rich leading content (items JSON does not support it — declarative children only).
Leading avatars (avatar example)
Trigger size and type
size (five tiers xs / small / medium / large / xl, sm/md/lg aliases accepted) and type (aligned with the oas-button type vocabulary) are forwarded to the trigger oas-button (a direct child). Dimensions not set on the dropdown keep the trigger button's own attributes; forwarding silently no-ops when the trigger is not an oas-button. Invalid size falls back to medium with a one-time warning.
size passthrough (five tiers)
type passthrough (aligned with button type)
Panel max height
max-height caps the menu panel height (numbers are px; forwarded to the inner oas-menu channel) — the panel scrolls internally, for long menus.
max-height=120 (panel scrolls internally)
Reserve Widest Option (reserve-width)
reserve-width: no jitter when switching (open the dropdowns to compare the two buttons)
reserve-width (opt-in) measures the max text width of all menu item labels and writes it to the trigger min-width: the trigger never jitters when switching values (high-frequency toolbar/filter switching). Both the items attribute and the declarative child channel participate; content changes re-measure automatically; removing the attribute restores the trigger width. The measurement is an approximation (trigger chrome margin tunable via the --oas-dropdown-reserve-pad variable, default 40px). The first button above has the reserve on (sized by the widest of the three options), the second is the control; open the menus and switch values to compare.
API
oas-dropdown
Attributes
| Attribute | Description | Type | Default |
|---|---|---|---|
arrow | Whether to show the arrow pointing at the trigger (arrow="false" hides it; the skeleton is kept) | string | true |
arrow-point-at-center | Pin the arrow to the panel center (by default the arrow follows the trigger's projection, so it still points at the trigger even after viewport-avoidance shifting) | boolean | — |
auto-adjust-overflow | Auto-flip/avoid when there is not enough viewport space (auto-adjust-overflow="false" disables it; the panel may overflow the viewport) | string | true |
close-on-scroll | Close the popover on page scroll (default repositions to follow; true closes on scroll) | boolean | — |
disabled | Disable entirely: no trigger response | boolean | — |
hide-on-click | Close after selecting an item (default true; false keeps it open for multi-select) | string | true |
hover-delay | Open delay in ms on hover trigger (default 150) | — | — |
hover-hide-delay | Close delay in ms on hover trigger (default 100) | — | — |
items | Menu items JSON | string | [] |
max-height | Caps the menu panel height with internal scrolling (numbers get px) | string | — |
offset | Gap in px between popover and trigger (default 8) | — | — |
open | Controlled display (boolean attribute; expands when present) | boolean | — |
placement | Popup placement | string | bottom |
reserve-width | Reserve the widest option width on the trigger (opt-in): measures the max text width of all menu item labels and writes it to the trigger min-width so switching values never jitters; re-measured automatically when items/children change; removing the attribute restores the trigger width | boolean | — |
size | Forwarded to the trigger oas-button size tiers (aliases normalized, invalid falls back to medium) | string | — |
split | Split button mode (boolean attribute): main button + arrow button; arrow opens the menu, main button fires oas-action | boolean | — |
trigger | Trigger: click (default) / hover / focus; space-separated for multiple (e.g. "click hover"). With hover, touch devices (coarse pointer) automatically degrade to tap toggling: tap to open, tap again or tap outside to close; desktop hover behavior unchanged | string | click |
type | Forwarded to the trigger oas-button type vocabulary | string | — |
value | Current selected value | string | — |
Events
| Event | Description |
|---|---|
oas-action | Main button clicked in split mode, detail: { originalEvent } |
oas-open-change | Popover open state changed, detail: { open: boolean } (including external setAttribute; controlled loop) |
oas-select | An item was selected, detail: { value } |
Slots
| Name | Description |
|---|---|
| default | Dropdown menu content (items / groups) |
CSS Variables
| CSS Variable | Default |
|---|---|
--oas-dropdown-arrow-height | 12px |
--oas-dropdown-arrow-radius | 0 |
--oas-dropdown-arrow-width | 12px |
--oas-glass-blur | none |
--oas-glass-ring | transparent |
--oas-origin-x | center |
--oas-origin-y | center |
oas-dropdown-item
Attributes
| Attribute | Description | Type | Default |
|---|---|---|---|
danger | Destructive item: red semantics (delete/exit etc.) | — | — |
disabled | Disable this item | — | — |
href | Link address: with href the item renders as a native <a> (real navigation + still emits oas-select) | — | — |
icon | Leading icon (icon name from the @oas-ui/icons registry) | — | — |
icon-color | Icon color: fixes the icon to this color (overrides the selected/disabled default); defaults to currentColor following the text color (only effective via the items JSON channel; the child-element channel does not parse this attribute) | — | — |
kind | Leaf semantics: radio (default, selectable) / action (no checked state, does not write back value) / checkbox (multi-select, value is the checked-set array) | — | — |
loading | Loading: renders a spinner and blocks clicks; recovers when data-driven | — | — |
rel | Link rel (with href) | — | — |
shortcut | Shortcut hint rendered as a trailing kbd (aligned with the menubar/menu shortcut contract) (only effective via the items JSON channel; the child-element channel does not parse this attribute) | — | — |
target | Link target (with href) | — | — |
value | Selected value (data-carrier field of the declarative child-element channel) | — | — |
Slots
| Name | Description |
|---|---|
| default | Dropdown item label content (default slot text); direct child <oas-dropdown-item> elements recursively become the submenu children |
template[slot="leading"] | Leading media template (or a direct child with slot="leading"): avatars/color chips/any rich leading content, rendered before the label and taking precedence over the icon attribute; replaced by the spinner in the loading state; declarative child channel only (not supported by the items JSON) |
oas-dropdown-group
Attributes
| Attribute | Description | Type | Default |
|---|---|---|---|
label | Group title (small, secondary color, not clickable) | — | — |
value | Radio-group id (selecting inside the group only updates that group's value) | — | — |
Slots
| Name | Description |
|---|---|
| default | Group items: child <oas-dropdown-item> elements flatten to the same level |
oas-dropdown-divider
Slots
| Name | Description |
|---|---|
| default | Divider data carrier (no attributes; the host parses it as type: "divider") |
Clicking the trigger toggles visibility; clicking outside / pressing Esc / selecting an item closes it. The floating menu is an inner oas-menu (role="menu", leaf items menuitemradio, items with submenus menuitem) supporting cascading submenus and keyboard navigation.