Performance and Tests
projectWhat the library measured for 0.9.0 against 0.8.1, and earlier: input latency against plain JavaScript, event dispatch, drag, startup, memory, bundle sizes and test counts, with the source and limits of each measurement.
On this page · 2 sections+−
These numbers come from the library’s own release reports. The latest is the 0.9.0 hardening report; the sections after it are earlier measurements, kept as history, and each says which report it comes from. The site does not measure the runtime; it copies the figures and keeps their source. Reactive CSS is not claimed to be faster than equivalent JavaScript.
All measurements ran in headless Chromium 156 (and, for tests, WebKit 27.2) on one development machine. Playwright’s Firefox does not start there, so Firefox is not covered by the automated tests; a manual pass of the examples in Chrome, Safari and Firefox was user-reported for 0.7.1, without logs or browser versions. Screen readers and real devices have not been verified for 0.9.0.
0.9.0 compared with 0.8.1
From the library’s docs/release-0.9.0-hardening.md (§D). The method runs v0.8.1, the first 0.9.0 candidate and the hardened 0.9.0 build on the same pages in one Chromium process, interleaved, five runs per cell, median. Absolute numbers move between sessions, so compare only within this table. Apple M4 Max, Chromium 156.
New 0.9 workloads, with no 0.8.1 equivalent:
- Drag: 0.178 ms of script per move with one draggable, 0.171 ms among 1,000 candidates. A hand-written handler takes 0.038 ms. No forced layout and no long task.
:on(pointermove): 12.2 ms of script per second at 60 events per second; plain JavaScript 3.3 ms.- Persisted drag: 21 storage writes during 300 moves (about 5 s), about 4 per second, with the last value kept.
- The 0.9 form pattern (
:on(input)withevent(value),bind(value)andcontent): 0.0259 ms per keystroke, doing two bindings’ work; plain JavaScript 0.0056 ms.
The report found no confirmed regression above its thresholds: more than 10% in time, more than 15% in memory, or long tasks over 50 ms. The click difference, about 4 µs, is within run-to-run variance. With the CPU throttled 4×, event-to-DOM latency stayed at a p95 of 0.7–0.8 ms in every version.
Sizes (same report, §J, after the final API cleanup): reactive-css.min.js is 149,039 B minified and 49,233 B gzip, against 117,830 B and 39,715 B for 0.8.1. The compact bundle is 42,488 B gzip. The cleanup itself changed no measured timing beyond run-to-run variation (§J). These are the report’s figures; the sizes of the current 0.9.0 build, measured when the site is built, are on Installation, and custom bundles on Custom bundles.
Tests (same report, §J, final): 196 unit tests and 848 browser tests passed across Chromium and WebKit, with 6 skipped; 427 passed in Chromium with the CPU throttled 4×.
These figures come from a build made before the final 0.9.0 syntax revision, which writes modifiers as prevent() and stop() and requests focus with focus:. They have not been measured again since.
Earlier measurements
Input to correct frame
The time from a real click to the first frame with the expected style, on a page with 100 widgets whose disabled attribute is derived with attr:, over 40 clicks. One 60 Hz frame is 16.7 ms. Measured for 0.7.0 (report §10); 0.7.1 and 0.8.0 did not repeat it.
The remaining cost is mostly the browser’s style recalculation for custom-property writes, which plain JavaScript writing the same properties pays too.
Startup, event dispatch and memory
- Starting 1,000 components (state, on: and text:) took 4.7 ms of script time with the core-only bundle and 6.2 ms with the full custom bundle, until the second frame, median of 7 loads; heap after GC was 1.68 to 2.14 MB. Bundle size hardly matters for startup at this scale (0.8.0 report §4). For 0.9.0, starting 10,000 components took 169.3 ms of task time with 0.9 syntax against 169.7 ms for 0.8.1, and no idle work was recorded (0.9.0 hardening report §D).
- About 4.5–5.5 µs per dispatched event for key() and 4.5–6 µs for click handlers, without prevent() (0.7.1 report §5); prevent() adds nothing measurable, and conditional on: rules cost about 5 µs more because the cascade is read to pick the rule (0.7.0 report §10). In 0.9.0 a click costs 0.0393 ms of script with :on() rules and 0.0378 ms with on:, against 0.0349 ms for 0.8.1 in the same run; the ranges overlap, and the DOM text is written in the same task as the click (0.9.0 hardening report §D).
- Memory test, 1,000 components × 150 cycles: 0 detached elements, 0 bindings left, heap growth −10 KB per cycle, end heap 3.55 MB (0.7.1 report §4). On custom bundles, 100 components × 50 cycles for each of the five profiles left no detached elements and no listener changes, and 300 cycles of basic and full kept the heap flat (1.76 → 1.82 MB and 2.32 → 2.51 MB, 0.8.0 report §4). In 0.7.0 one run reported +121 KB per cycle that two repeats did not reproduce; the report states it rather than explaining it away (0.7.0 report §10).
- Latest report: the library's
docs/release-0.9.0-hardening.md. Headless Playwright 1.64.1 engines, Chromium 156 and WebKit 27.2, on one development machine (Apple M4 Max, macOS 27.0, as recorded in the library’s benchmarks/AUDIT-0.4.md). Each figure names the report and section it comes from.
Bundle sizes
In bytes, from the 0.8.0 report (§4) and the 0.8.1 preparation report (§4). In 0.8.1 the standard bundle grew by 1.5 KB for the pre-beta stabilization fixes (mostly the :changed() animation tracking and the bind: fix); the compact bundle drops the describe() descriptions. Smaller bundles with only some features are measured on Custom bundles.
Tests
From the 0.8.1 preparation report (§6).
What the tests cover, engine by engine, is on Browser Compatibility. The previews on this site run the same bundle: the site’s unit tests check that its version matches the library’s package.json, describe() and the CHANGELOG.
For where these numbers fit in the project’s direction, see the Vision page.