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

Introduction

guide

Reactive CSS adds state, events, conditions and bindings to the CSS you already write. Add one script tag, and many common interactions need no JavaScript of your own.

On this page · 6 sections+

What it is

Reactive CSS reads your stylesheets and compiles the reactive parts to standard CSS plus custom properties, or to native CSS such as if() where that is equivalent. It then updates only the elements that use them. The browser still does all styling, transitions and animations.

Its syntax is an extension that the runtime interprets, not standard CSS: without the script, browsers ignore it. It adds a small set of composable primitives:

  • state() declares a value an element owns;
  • event rules such as .button:on(click) change states, with mutations like --open: toggle();
  • when() and :when() read states in conditions;
  • bind(), --attr-* and content connect states to form controls, attributes and text.
/* The state is reflected into aria-pressed. */.toggle {  --on: state(false, bind(aria-pressed));}.toggle:on(click) {  --on: toggle();}.toggle .label {  content: when(--on, "On", "Off");}.toggle:when(--on) .knob {  background: #15803d;}.toggle:when(--on) .knob::after {  translate: 20px 0;}
state()Function · hover any red tokenMakes each matching element the owner of an independent state with an initial value.Reference →
Resultnative button, aria-pressed
waiting for interaction

The first rule declares the state and reflects it into aria-pressed, the :on(click) rule changes it, and the other rules read it. Hover any vermilion token to see what it does.

Why it exists

Many interface details are small state machines. A switch is on or off, a panel is open or closed, one tab is selected. Today each one usually needs a script that listens for an event, stores a value and writes a class or attribute, while the stylesheet separately describes what that class looks like.

Reactive CSS lets one stylesheet hold both halves. The value, the event that changes it and the styles that depend on it sit together, so there is no class name to keep in sync between two files.

The same switch, two ways

switch.js
const el = document.querySelector('.switch');el.addEventListener('click', () => {  const on = el.getAttribute('aria-checked') !== 'true';  el.setAttribute('aria-checked', String(on));});
switch.cssreactive
.switch {  --on: state(false, bind(aria-checked));  background: when(--on, #f2542d, #d9d5cf);}.switch:on(click) {  --on: toggle();}
state()Function · hover any red tokenMakes each matching element the owner of an independent state with an initial value.Reference →

Both keep aria-checked in sync with the switch. The JavaScript version styles the switch from the attribute in a separate stylesheet; the CSS version keeps the state, the event and the style in one place. A native <input type="checkbox" role="switch"> needs neither.

What it is not

  • Not a framework, not a JavaScript replacement, and not a programming language inside CSS.
  • Not a duplicate of native CSS. Math, colors, :has(), @container, if(), :focus-visible and so on stay native. See the feature policy.
  • Not a CSS standard, and not endorsed by any browser vendor. It is an experiment in what CSS could do.
  • Not an accessibility layer. It can write a few allowlisted attributes (--attr-* and bind(), such as aria-expanded, aria-pressed and disabled) and move focus after a user action (focus:). It does not announce changes, and setting attributes does not make a component accessible by itself.

Many interactions still need JavaScript: data, routing, complex focus management and real accessibility semantics.

Note · Status

0.9.0 is an experimental release. It adds the 0.9 syntax: event rules with :on(), bind() in state(), --attr-*, reactive content, drag, persist() and :blur. The 0.8 syntax keeps working; see Migrating from 0.8. Features are tested automatically in Chromium and WebKit; Firefox, real Safari, touch hardware and screen readers are not verified yet (Browser compatibility). Features marked exp in the reference are experimental, among them persist(), reactive content, drag, focus: and the --attr-* namespace.

What you can build

Tabs, accordions, toggles, counters, live previews, dropdowns, simple settings forms that survive a reload, draggable cards and sliders, and small motion and geometry effects.

Next steps

Experimental · syntax may change between minor versions