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

bind() in state()

declarationsince 0.9.0requires runtime

state(v, bind(value)) and state(v, bind(checked)) keep a form control and its own state equal in both directions. bind(<attribute>) reflects a state into one of its owner’s attributes.

On this page · 10 sections+

Syntax

--<state>: state(<initial>, bind(value | checked));--<state>: state(<initial>, bind(<attribute>));

registry syntax: --state: state(initial, bind(value | checked))

Parameters

bind(value)two-wayone of
Text-like inputs and <textarea> hold a string; number and range inputs a number or dimension; a single <select> a string, keyword or number.
bind(checked)two-wayone of
A checkbox, with a boolean state.
bind(<attribute>)one-way, state → attributeone of
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-*.

Effect

No value. With value or checked, the control writes the state on input or change, and a mutation of the state updates the control. With an attribute, every change of the state is written to the owner’s attribute.

Example

/* State that the preview line reads lives on the form, the shared owner. */.signup {  --greeting: state("");  --news: state(false);}/* The field also owns a two-way state of its own. */.signup input[name=name] {  --name: state("", bind(value));}/* Copy the control's value up to the form at input time. */.signup input[name=name]:on(input) {  --greeting: set(event(value));}.signup input[name=news]:on(change) {  --news: set(event(checked));}/* The field styles itself from its own state. */.signup input[name=name]:when(--name != "") {  border-color: #15803d;}.signup .greeting {  content: when(--greeting == "", "stranger", "");}.signup:when(--greeting != "") .greeting {  content: var(--greeting);}.signup .sub {  content: when(--news, " Newsletter: yes.", "");}
state()Function · hover any red tokenMakes each matching element the owner of an independent state with an initial value.Reference →
Resultlabelled controls
waiting for interaction

Correct and incorrect usage

correctReflect a toggle into aria-pressed

toggle.cssreactive
.toggle { --on: state(false, bind(aria-pressed)); }.toggle:on(click) { --on: toggle(); }.toggle .label { content: when(--on, "On", "Off"); }

The button’s aria-pressed follows the state. If the HTML says aria-pressed="true", the state starts as true.

incorrectA sibling reading the field’s own state

field.cssreactive
.field input { --q: state("", bind(value)); }.field output { content: var(--q); }

This compiles, but the output never sees --q: the state is owned by the input, and the output is not inside it. Declare --q: state("") on .field and use .field input:on(input) { --q: set(event(value)); }.

incorrectbind() as the initial value

menu.cssreactive
.menu > button { --open: state(bind(aria-expanded)); }

A compile error: the state needs an initial value for the case where the attribute is missing or invalid.

Behavior

  • Release status. The 0.9.0 release recommendation (hardening report, section H) lists bind(value) and bind(checked) in state() as stable, so this page does not mark it experimental. The runtime's describe() still reports experimental until the registry is updated. bind(<attribute>) has been experimental since 0.5.2.
  • The control is the owner. The state belongs to the control the rule matches. Only that control and its descendants can read it: a label, an output or any other sibling cannot. For a state that other elements read, declare it on a shared ancestor and copy the value up at event time with input:on(input) { --q: set(event(value)); }, as the example does.
  • Initialization. A valid persisted value first; otherwise the control’s value if the user or the browser already edited it (autofill, a restored form); otherwise the initial value. The result is then written to the control.
  • Typing never rewrites the field, so the caret and the selection stay. Form reset restores the initial value.
  • bind(<attribute>) reflects the state into the owner’s attribute with HTML semantics: boolean attributes present or removed (never disabled="false"), ARIA states as "true" or "false", data-* as text. A valid attribute value in the HTML initializes the state once, unless a persisted value exists. Later changes made by scripts are not read back.
  • The modifiers come in either order, each at most once: state("", bind(value), persist("name")) equals state("", persist("name"), bind(value)). A state has one bind().

Supported contexts

  • Unconditional rules only: no @media, @supports, @container, :hover, :focus or :when(), because a binding lasts as long as the element matches.
  • Trusted stylesheets only.

Limitations

  • state(bind(aria-expanded)), a bind() without an initial value, is rejected: write state(false, bind(aria-expanded)).
  • bind(checked) needs a boolean state; bind(value) needs a string or number state.
  • Never binds password, one-time-code or payment fields.
  • Multiple selects, file, date and color inputs are not supported.

Examples

Experimental · syntax may change between minor versions