Dropdown
stimeo--dropdown
A region toggled by a button. The open state, Esc, and outside clicks are handled for you.
The plainest way to toggle a region from a button. The content is yours: a set of links, a form, anything. Whether it is open is announced to screen readers. Esc closes it and returns focus to the button, and a click outside closes it too. If you need an application menu where the arrow keys walk the commands, use the menu instead.
Keyboard
| Key | Action |
|---|---|
| Enter / Space | Toggle the menu while the trigger is focused (native button). |
| Esc | Close the menu and return focus to the trigger. |
<%# Markup for the dropdown demo.
The single source used for both the live render and the copy-paste code.
Connect to stimeo--dropdown via data-controller / data-*-target / data-action.
The behavior is the library's; the look (classes) is the Playground's CSS. %>
<div class="dropdown" data-controller="stimeo--dropdown">
<button
type="button"
class="demo-trigger"
aria-expanded="false"
data-stimeo--dropdown-target="trigger"
data-action="click->stimeo--dropdown#toggle">
<%= t("components.dropdown.demo.trigger") %>
</button>
<%# The toggled menu. Starts hidden (closed).
Each link uses click->stimeo--dropdown#close to close the menu on selection.
(The links are inside the controller element, so an outside click won't close
it — wire it explicitly.) %>
<ul class="dropdown__menu" data-stimeo--dropdown-target="menu" hidden>
<li><a class="demo-menuitem" href="#profile" data-action="click->stimeo--dropdown#close"><%= t(
"components.dropdown.demo.profile"
) %></a></li>
<li><a class="demo-menuitem" href="#settings" data-action="click->stimeo--dropdown#close"><%= t(
"components.dropdown.demo.settings"
) %></a></li>
<li><a class="demo-menuitem" href="#sign-out" data-action="click->stimeo--dropdown#close"><%= t(
"components.dropdown.demo.sign_out"
) %></a></li>
</ul>
</div>
/*
* Presentation-only styles for the dropdown demo.
* Stimeo UI itself ships behavior only (no CSS), so styling that makes the demo
* readable lives here. When adding a new component, create its
* demos/<name>/demo.css the same way.
*/
.dropdown {
position: relative;
display: inline-block;
}
.dropdown__menu {
position: absolute;
left: 0;
margin: 0.25rem 0 0;
padding: 0.25rem;
list-style: none;
min-width: 12rem;
background: var(--surface-card);
border: 1px solid var(--border-strong);
border-radius: 0.375rem;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
}
.dropdown__menu li {
margin: 0;
}
This demo needs no consumer-side JS (the controller handles the behavior).
These demo styles use shared design tokens (light + dark). Copy the shared styles too, then toggle data-theme on your root element for dark mode.
The data-* attributes you add to your own HTML to wire this component. Put the data-controller below on a root element, then place its targets / values / actions inside that element.
On the root element
data-controller="stimeo--dropdown"
Targets
| Name | Description | Attribute |
|---|---|---|
trigger
required
|
The button that toggles the menu; its aria-expanded reflects open/closed state. |
data-stimeo--dropdown-target="trigger" |
menu
required
|
The disclosed region whose hidden attribute reflects visibility. | data-stimeo--dropdown-target="menu" |
Actions
| Name | Description | Action |
|---|---|---|
close
|
Hides the menu and sets the trigger's aria-expanded to false. |
stimeo--dropdown#close |
open
|
Reveals the menu and sets the trigger's aria-expanded to true. |
stimeo--dropdown#open |
toggle
|
Toggles the menu between open and closed. | stimeo--dropdown#toggle |
State hooks
The library only manages these ARIA/data attributes and custom properties. Your CSS reads them to render the look — selectors like [aria-selected], [aria-expanded], or var(--stimeo--…) hook into this state.
| Hook | Target | Meaning |
|---|---|---|
aria-expanded |
Trigger | "true" while the menu is open, "false" when closed. |
hidden |
Menu | Removed when open, present when closed. |