{% comment %} Server-driven dropdown. The trigger dispatches `toggle_dropdown`; openness is the host LiveView's `is_open`, and every open/closed artefact is rendered from it — the menu's `display`, `aria-expanded`, and the `data-open` attribute the chevron rotation is keyed on (`theming/css/components.css`), which `51-keyboard-nav.js` also reads to decide whether the menu is open. `data-theme-dropdown` below is the client fallback for plain pages, and `components.js` stands down on any page with a djust mount root so a LiveView page uses only the server path. Both hooks coexist deliberately: the theming gallery cannot be a LiveView (its 25 `theme_*` tags are Django-engine only), so dropping the fallback would leave it with a dead dropdown. `data-open="true"` rather than a bare attribute: `theming/css/components.css` matches on presence and would accept either, but the app-level `djust_components/components.css` matches `[data-open="true"]` and `51-keyboard-nav.js` handles both — emitting the valued form satisfies all three. Known gap, unchanged here: the `slot_trigger` branch renders the caller's markup verbatim, so it carries no `dj-click` and therefore no server event — and no `aria-haspopup` / `aria-expanded`, which `DROPDOWN_CONTRACT` requires. A slot-provided trigger is inert. That predates this change; fixing it means deciding whether the contract should be enforced on caller-supplied markup. {% endcomment %}