Core · Actions and input
Dropdown
A button that opens a short list of actions or links, anchored under it.
Example
A button called Actions that opens a short list: edit, duplicate, move and delete a project.
Press Escape or click outside to close it.
Known issues
There are no arrow keys: Tab moves between the actions, as in any list of buttons. A browser without CSS anchor positioning opens the list as a sheet at the bottom of the screen instead of under the button.
Code
Copy the markup
<div class="luz-dropdown">
<button class="luz-btn luz-btn--secondary" type="button" popovertarget="project-actions">Actions</button>
<div class="luz-dropdown__menu" id="project-actions" popover>
<ul class="luz-dropdown__list">
<li><a class="luz-dropdown__item" href="/project/edit/">Edit</a></li>
<li><button class="luz-dropdown__item" type="button">Duplicate</button></li>
<li><hr class="luz-dropdown__separator"></li>
<li><button class="luz-dropdown__item luz-dropdown__item--danger" type="button">Delete project</button></li>
</ul>
</div>
</div>
Variants and options
| Class or attribute | What it does |
|---|---|
.luz-dropdown | The wrapper: the button and its panel, side by side in the source. It scopes the anchor, so several dropdowns on a page each open under their own button |
popovertarget and popover | The button names the panel by its id; the panel is the popover. The browser does the rest |
.luz-dropdown | The panel: a surface with an edge and a shadow, at least 12 rem wide, scrolling when it is taller than 24 rem |
.luz-dropdown | The <ul> of actions |
.luz-dropdown | A link or a button, at least 44 px high and the width of the panel |
.luz-dropdown | A destructive action, in the danger color |
aria-disabled="true" | An action that is not available now: disabled text color, and it keeps its place in the tab order so a reader can find out it exists |
.luz-dropdown | A rule between two groups of actions, an <hr> in its own <li> |
How it works
A native popover opened by a button with popovertarget, styled by components/dropdown.css. There is no JavaScript.
- The browser puts the panel in the top layer, closes it on Escape or a click outside, returns the focus to the button and tells assistive technology whether it is expanded.
- Where the browser has CSS anchor positioning (Chrome and Edge 125, Safari 26, Firefox 147) the panel opens under the button at its start edge, and flips above when there is no room. Without it the panel is a sheet at the bottom of the screen.
- In a browser without the popover API the list stays in the page, under the button, and every action still works.
- It is a list of actions, not an ARIA
menu: that role promises arrow keys, a roving tabindex and typeahead, which LUZ does not script. Plain links and buttons keep every behaviour the browser gives them.
Without JavaScript
Nothing changes: the popover is HTML. The actions that are buttons need your own script to do something, as any button does; the ones that are links work as they are.
Keyboard and ARIA
Enter or Space on the button opens the panel; the focus stays on the button, and the panel follows it in the tab order. Tab moves to the first action, then through the others, and Shift+Tab goes back.
Escape closes the panel and returns the focus to the button.
- Give the button a name that says what is inside: “Actions”, “More options for Ana”, not a bare “…”.
- An unavailable action uses
aria-disabled="true"and nodisabled, so it stays reachable and a screen reader says it exists. Do not run its action when it is activated. - The panel is not a dialog and holds no form: for a decision or a form, use the modal.
Contrast and focus are in the Design tab.
Design
When to use
Use a dropdown for three to seven related actions on one thing, when showing them all would crowd the page: the actions of a row in a table, of a card, of a project.
When not to use
Do not use a dropdown to pick a value, or to hide the main action.
- To pick one value of a form, use a select.
- For the main action of a page, use a visible button.
- For the sections of a site, use a sidebar or a navigation list.
- LUZ has no context menu on right click. The browser's own menu is the reader's, and replacing it breaks copy, paste and spelling help; give the same actions in a dropdown.
Do and don't
Writing the content
Start each action with a verb, in the order a reader would look for it.
- Two or three words: “Duplicate”, “Move to archive”.
- Say what the destructive action destroys: “Delete project”, not “Delete”.
- Group with a separator, not with headings.
Contrast and focus
Actions are primary text on the surface of the panel; the panel has an edge and a shadow.
| Part | Colour on background | Light | Dark | High contrast | Needs |
|---|---|---|---|---|---|
Action | --luz-text-primary on --luz-surface | 16.5:1 | 14.0:1 | 21.0:1 | 4.5:1 |
Action on hover | --luz-text-primary on --luz-surface-hover | 14.4:1 | 12.6:1 | 20.1:1 | 4.5:1 |
Action pressed | --luz-text-primary on --luz-surface-pressed | 12.8:1 | 10.9:1 | 18.0:1 | 4.5:1 |
Danger action | --luz-color-danger on --luz-surface | 5.5:1 | 10.8:1 | 9.3:1 | 4.5:1 |
Disabled action | --luz-text-disabled on --luz-surface | 3.7:1 | 3.9:1 | 5.3:1 | 3:1 |
Panel edge | --luz-outline on --luz-surface-page | 4.3:1 | 3.6:1 | 21.0:1 | 3:1 |
Focus ring | --luz-focus-ring on --luz-surface | 5.9:1 | 7.2:1 | 21.0:1 | 3:1 |
Measured from the token values of each mode, rounded down. Needs is the WCAG 2.2 AA minimum: 4.5:1 for text, 3:1 for focus rings, borders, icons and the disabled state.
- Focus: each action has a 2 px
--luz-focus-ringoutline drawn inside its row, so the panel's edge does not clip it. - Not colour alone: the danger action is named for what it destroys, and the unavailable one is announced as disabled.
- Forced colors: a hovered action is underlined and an unavailable one is
GrayText.
Tokens it uses
The 19 tokens that components/dropdown.css reads, by family. Each family links to its page, with the values in every mode. In Figma they are variables with the same names: see Tokens in Figma.
- Color
--luz-color-danger,--luz-divider,--luz-focus-ring,--luz-outline,--luz-surface,--luz-surface-hover,--luz-surface-pressed,--luz-text-disabled,--luz-text-primary- Spacing and sizing
--luz-control-lg,--luz-space-2xs,--luz-space-md,--luz-space-sm- Typography
--luz-font-sans,--luz-text-md- Radius
--luz-radius-md,--luz-radius-sm- Shadows
--luz-shadow-md- Motion
--luz-duration-fast
Keyboard and ARIA are in the Code tab.