Documents v0.9.0 · previews run the real runtime · experimental features are marked exp

Dropdown menu

A menu that opens on click and closes on Escape, on a choice or on a click anywhere else.

Live previewreal runtime

Open the menu, choose an item, open it again and press Escape, then open it and click outside.

How it works

  • state()line 2Makes each matching element the owner of an independent state with an initial value.
  • outside()line 9An event for :on() that happens outside the matched element: a click, a pointer press or focus moving elsewhere on the page.
  • :on()lines 4, 7, 8, 9An event rule: when the event happens on the matched element (or inside it, for bubbling events), the rule’s state mutations run. The cascade picks one winning mutation per state, per trigger and per element.
  • attr()line 7In state() and in mutations: reads an HTML attribute into a state. Everywhere else attr() is the browser’s native function.
  • --attr-*line 11--attr-<name>: <value> writes one allowlisted HTML attribute of the matched element from a reactive value: ARIA states such as aria-expanded, aria-checked or aria-disabled, disabled, hidden, tabindex or data-*. The only custom-property namespace with DOM semantics.
  • focus:lines 15, 16Moves focus to the matched element when its value goes from false to true because of a user action. In 0.9 this declaration is the focus request: focus: when(--open).

Three ways to close one menu, and focus that goes where the user expects. outside(click) closes the menu on a click anywhere else on the page, Escape closes it, and a choice closes it and records which item was chosen.

Native CSS alternative

The popover attribute with popovertarget gives light dismiss and Escape without a script. Prefer it for a plain menu; this version also records the choice and manages focus.

Limitations

  • Arrow keys do not move between items: Tab and Shift+Tab do.

Implementation

2 files · 18 lines
style.cssreactive
/* ---- Reactive CSS -------------------------------------------------------------------------- */.dropdown { --open: state(false); --back: state(false); --choice: state("none"); }.dropdown .trigger:on(click) { --open: toggle(); --back: set(false); }/* A choice, Escape or a click anywhere else closes it. */.dropdown li button:on(click) { --choice: set(attr(data-choice)); --open: set(false); --back: set(true); }.dropdown .menu:on(keydown(Escape)) { --open: set(false); --back: set(true); }.dropdown .menu:on(outside(click)) { --open: set(false); }.dropdown .trigger { --attr-aria-expanded: var(--open); }.dropdown:when(--open) ul { display: block; }/* Opening focuses the first item; Escape or a choice returns focus to the button. */.dropdown li:first-child button { focus: when(--open); }.dropdown .trigger { focus: when(--back); }.dropdown .choice { content: var(--choice); }
state()Function · hover any red tokenMakes each matching element the owner of an independent state with an initial value.Reference →

Source: site/src/catalog/interfaces/dropdown/. style.css from its Reactive CSS divider; the layout above it and the shared api.css run but are not shown.

Related

Signup form

A text field, a select and a checkbox feed a live preview, and Reset clears both.

experimentalstate():on()event()content:when()

Step wizard

Three steps with Back and Next buttons, a step list you can click, and aria-current on the current step.

experimentalstate():on()set():nth-var()--attr-*

Task list

Native checkboxes whose label fades when done, with a short animation when a task is checked or unchecked.

state()event():just-checkedwhen()