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

--attr-*

declarationsince 0.9.0experimentalrequires runtime

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

On this page · 10 sections+

Syntax

--attr-<attribute>: <value>;

registry syntax: --attr-<attribute>: <string> | <number> | <keyword> | var(--state) | when(condition [, a, b])

Parameters

attributelower-case allowlisted namerequired
aria-expanded, aria-pressed, aria-selected, aria-disabled, aria-hidden, aria-busy, aria-invalid, aria-checked, aria-current, aria-valuenow, aria-label, aria-valuetext, disabled, hidden, required, tabindex (0 or -1) or data-*.
valueone scalarrequired
A literal, var(), previous(), direction() or when(); a native if() also works. when(condition) without branches gives true or false.

Effect

No value. The attribute is written in the same task as the action that changed the state, so a derived disabled is in place before the next click.

Example

.item {  --open: state(false);}.item > button:on(click) {  --open: toggle();}/* Writes aria-expanded="true" or "false" on the button. */.item > button {  --attr-aria-expanded: var(--open);}.item .body {  display: none;}.item:when(--open) .body {  display: block;}.item .chevron {  rotate: when(--open, 180deg, 0deg);}
state()Function · hover any red tokenMakes each matching element the owner of an independent state with an initial value.Reference →
Resultnative buttons, aria-expanded
waiting for interaction

Correct and incorrect usage

correctDisable a button at a limit

counter.cssreactive
.counter { --count: state(0); }.counter .dec:on(click) { --count: decrement(1); }.counter .dec { --attr-disabled: when(--count <= 0); }

The disabled attribute is present while the count is 0 or less and removed otherwise, so the button cannot take the count below zero.

correctA state name as a data attribute

panel.cssreactive
.panel {  --open: state(false);  --attr-data-state: when(--open, "open", "closed");}

Writes data-state="open" or data-state="closed" for other CSS or scripts to read.

incorrectA URL attribute

link.cssreactive
.link { --attr-href: "/next"; }

URL attributes are never writable, so nothing is written and the declaration stays a plain custom property.

incorrectAn upper-case suffix

menu.cssreactive
.menu { --open: state(false); }.menu > button { --attr-aria-Expanded: var(--open); }

Custom property names are case-sensitive, attribute names are not. Write --attr-aria-expanded.

Behavior

  • Experimental as a namespace; the attribute semantics have been tested since 0.5.3.
  • Reserved namespace. Every custom property named --attr-* is claimed by Reactive CSS. An allowlisted name writes that attribute; any other name gives a diagnostic and stays a plain custom property. Without the prefix, names such as --disabled or --aria-expanded have no DOM effect. A state cannot be named --attr-*.
  • Values. HTML boolean attributes (disabled, hidden, required): true sets the attribute and false removes it; a boolean state’s 1 and 0 work too. ARIA booleans are written as "true" or "false"; aria-checked also takes mixed; aria-current takes page, step, location, date, time, true or false; aria-valuenow a number; aria-label, aria-valuetext and data-* any text. Other values are reported and not written.
  • Restore. With no applicable declaration, or a missing or empty winning value, the attribute returns to what it was before the first write. If something else changed it in the meantime, the binding yields and writes no more.
  • One property per attribute. Declarations for different attributes combine, and the cascade picks the winner for each attribute separately.
  • No loops. Attributes written this way never re-match reactive rules, and attributes never feed state.
  • disabled and required are written only where HTML gives them a meaning (a warning otherwise).

Supported contexts

  • Style rules in trusted stylesheets, on any element a rule matches. The value usually reads state owned by an ancestor.
  • Not inside :on() rules: an attribute follows a state, it does not happen at an event.

Limitations

  • Compatibility warning. A stylesheet that already uses --attr-* custom properties for something else now writes attributes when the name is allowlisted: --attr-hidden: 1 hides the element, --attr-data-x writes data-x. Rename such properties before upgrading. This is the one change in 0.9 that can alter a working 0.8 page; see Migration.
  • Never writable: event handlers (on*), URL attributes (href, src, action, formaction, srcset, xlink:href), style, id, class, name and value, as well as role and ID references such as aria-describedby.
  • The suffix must be lower case. Custom property names are case-sensitive, so --attr-aria-Expanded is a diagnostic and nothing is written.
  • A state(…, bind(<attribute>)) on the same element owns that attribute; an --attr-* declaration for it is ignored with a diagnostic.
  • Setting attributes does not make a component accessible by itself.

Examples

Experimental · syntax may change between minor versions