Documents v0.9.0 · previews run the real runtime · experimental features are marked exp
Shopping cart
Three cart lines with − / + / Remove, a subtotal per line, the item count and total, an "empty cart" message, and Checkout and "Empty cart" buttons that are disabled while the cart is empty. Local only: Checkout does nothing.
increment()lines 8, 9, 10Adds a step to a number or dimension state. In an :on() rule it is the mutation --count: increment(1).
decrement()lines 11, 12, 13Subtracts a step from a number or dimension state. In an :on() rule it is the mutation --count: decrement(1).
set()lines 14, 15, 16, 41Sets a state to a value. In an :on() rule it is a mutation of the state on the left, --x: set(5), with typed expressions.
attr:lines 8, 9, 10, 11…Derives one allowlisted HTML attribute of the matched element, such as disabled or aria-expanded, from a reactive value.
Several quantities with derived native disabled at each bound, cross-element dependencies (Checkout depends on every line), and totals computed by native calc() instead of aggregate functions.
Left to native HTML and CSS
Math:calc() on integers, and registered @property integers so the browser computes each number before text: writes it.
Everything else:disabled and hidden semantics, :disabled and [data-empty] styling, the responsive grid.
What would normally need JavaScript
All of it: quantities, subtotals, totals, enabling and disabling buttons. CSS alone cannot count clicks.
Limitations
A fixed number of lines. The cart owns one state per line (--q1…--q3) because the total needs them all, and CSS has no way to sum values owned by different elements. A cart with a dynamic number of lines needs script (or a server-rendered total). This is the “no aggregate functions” boundary.
Rules repeat per line. No parameterized rules.
Whole-dollar prices.text: prints one integer; cents would need formatting (no number formatting in CSS).
Prices live in CSS.--p1…--p3 are CSS numbers. Real prices must come from the server, and the server must compute the charged total.
Focus. Reaching 0 or 9 disables the focused button, and focus returns to the page. CSS cannot move focus.
Accessibility
Buttons with accessible names (“Add one notebook”), grouped per line, visible focus.
Native disabled at bounds and on Checkout: announced as unavailable, skipped by Tab (tested).
Totals are DOM text in a polite live region. Announcements not verified with screen readers.
Implementation
2 files · 42 lines
style.cssreactive
/* ---- state: one quantity per line, owned by the cart ------------------------------------------- The total needs every quantity, and CSS cannot sum values owned by different elements: the cart owns all three (no aggregate functions, no arrays). Prices are CSS numbers. Initial values repeat the HTML. */.cart{--q1:state(1);--q2:state(0);--q3:state(2);--p1:12;--p2:30;--p3:8;}/* One rule per line: CSS has no parameterized rules, and each line needs its own state name. */.cart.l1.plus{on:click(increment(--q1));attr:disabledwhen(--q1>=9);}.cart.l2.plus{on:click(increment(--q2));attr:disabledwhen(--q2>=9);}.cart.l3.plus{on:click(increment(--q3));attr:disabledwhen(--q3>=9);}.cart.l1.minus{on:click(decrement(--q1));attr:disabledwhen(--q1<=0);}.cart.l2.minus{on:click(decrement(--q2));attr:disabledwhen(--q2<=0);}.cart.l3.minus{on:click(decrement(--q3));attr:disabledwhen(--q3<=0);}.cart.l1.remove{on:click(set(--q1,0));attr:disabledwhen(--q1==0);}.cart.l2.remove{on:click(set(--q2,0));attr:disabledwhen(--q2==0);}.cart.l3.remove{on:click(set(--q3,0));attr:disabledwhen(--q3==0);}.cart.l1{attr:data-emptywhen(--q1==0,"true","false");}.cart.l2{attr:data-emptywhen(--q2==0,"true","false");}.cart.l3{attr:data-emptywhen(--q3==0,"true","false");}.line[data-empty="true"].name{color:#888;text-decoration:line-through;}/* ---- numbers: native CSS math on integers, written as real DOM text (text:) -------------------- */@property--s1{syntax:"<integer>";inherits:true;initial-value:0;}@property--s2{syntax:"<integer>";inherits:true;initial-value:0;}@property--s3{syntax:"<integer>";inherits:true;initial-value:0;}@property--items{syntax:"<integer>";inherits:true;initial-value:0;}@property--total{syntax:"<integer>";inherits:true;initial-value:0;}.cart{--s1:calc(var(--q1)*var(--p1));--s2:calc(var(--q2)*var(--p2));--s3:calc(var(--q3)*var(--p3));--items:calc(var(--q1)+var(--q2)+var(--q3));--total:calc(var(--s1)+var(--s2)+var(--s3));}.cart.l1.qty{text:var(--q1);}.cart.l1.sub.n{text:var(--s1);}.cart.l2.qty{text:var(--q2);}.cart.l2.sub.n{text:var(--s2);}.cart.l3.qty{text:var(--q3);}.cart.l3.sub.n{text:var(--s3);}.cart.items{text:var(--items);}.cart.total{text:var(--total);}/* ---- cart-wide: checkout and "Empty cart" are disabled for an empty cart ------------------------ */.cart.checkout,.cart.clear{attr:disabledwhen(--q1==0and--q2==0and--q3==0);}.cart.clear{on:click(set(--q1,0),set(--q2,0),set(--q3,0));}.cart.empty{attr:hiddenwhen(--q1==0and--q2==0and--q3==0,false,true);color:#555;}
state()Function · hover any red tokenMakes each matching element the owner of an independent state with an initial value.Reference →
Source: the library's examples/shopping-cart/. style.css from its first section comment; the layout above it runs but is not shown. Written in the 0.8 syntax (on:, bind:, text:, attr:), which 0.9.0 still runs; see Migrating from 0.8 for the 0.9 forms.
A sign-up form using native constraint validation: required fields, type="email", minlength. A company field appears and becomes required only for business accounts. Submit is disabled until the terms are accepted, and error messages appear after the first submit attempt. Nothing is sent.
A focusable pad moved with the arrow keys (Escape centers it), focus tracking with focusin/focusout, nested regions that show how one key press reaches several handlers, and a Reset button that is disabled while there is nothing to reset.
Three panels switched by buttons, Previous/Next and a native popover menu. Focus follows user-initiated panel changes to the new panel's heading (or to Confirm on panel 3); a hidden section is revealed and focused in one action; a locked (disabled) Confirm and the open popover deliberately keep focus where it is.