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

focus:

declarationsince 0.6.0experimentalrequires runtime

Moves 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).

On this page · 10 sections+

Syntax

focus: when(<condition>) | var(<boolean state>) | true | false;

registry syntax: focus: when(condition) | var(--boolean) | true | false

Parameters

valuebooleanrequired
Usually when(<condition>), which gives true or false. Or false in a base rule and true in a rule for the active item.

Effect

No value. Focus moves once per false → true transition; a value that stays true never refocuses.

Example

.search {  --open: state(false);}.search .toggle:on(click) {  --open: toggle();}.search .toggle {  --attr-aria-expanded: var(--open);}.search .field {  display: none;}.search:when(--open) .field {  display: block;}/* A focus request: when --open becomes true, the field gets focus. */.search .field {  focus: when(--open);}/* From a real focus loss until the next focus. */.search .field:blur {  animation: nudge 300ms;}@keyframes nudge {  25% { translate: -4px 0; }  75% { translate: 4px 0; }}
state()Function · hover any red tokenMakes each matching element the owner of an independent state with an initial value.Reference →
Resultnative button and search field
waiting for interaction

Correct and incorrect usage

correctFocus the heading of the active step

wizard.cssreactive
/* <h2 tabindex="-1"> in the HTML */.wizard { --step: state(1); }.wizard .panel > h2 { focus: false; }.wizard .panel:nth-var(--step) > h2 { focus: true; }

The true declaration moves from one heading to the next as --step changes, and each change is a false → true transition for the new heading. The library never adds tabindex, so the HTML provides it.

incorrectA functional :focus()

dialog.cssreactive
.dialog { --open: state(false); }.dialog input:focus(when(--open)) {}

Rejected: :focus is the native pseudo-class and takes no arguments. Write .dialog input { focus: when(--open); }.

Behavior

  • 0.9. There is no functional :focus(…): .field:focus(when(--open)) {} is rejected with a diagnostic, so write focus: when(--open) in the rule. Native :focus, :focus-visible and :focus-within are untouched.
  • Only state changes made during a trusted click, keydown, input or change arm a transition, and since 0.9 also pointerup and submit, which covers :on(pointerup), :on(submit) and the end of a drag.
  • Never on page load, insertion, scripts, timers, form reset, focus events, pointer enter/leave or outside().
  • Never overrides focus the user moved since the event, focus already inside the target, or an open <dialog> or popover that does not contain the target.
  • Resolved after the state’s CSS and the --attr-* and content writes; the first eligible target in document order wins.
  • After the keydown of Enter or a printable key, focus moves once the key press has finished (next frame), so Enter in a field never also clicks the button that receives focus.

Supported contexts

  • Targets must be rendered, not disabled or inert, and focusable. A non-focusable target is reported; the library never adds tabindex.
  • Trusted stylesheets only.
  • In a custom bundle: the focus feature, which adds events. focus: when(…) also needs conditions for when(), which the analyzer adds; focus: var(--open), true and false do not.

Limitations

  • Not verified with screen readers.
  • No focus(selector) action.
  • Several events before one frame are judged by the final value only.

Examples

Experimental · syntax may change between minor versions