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

Performance and Tests

project

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

Caution · Read these numbers with

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.

Scenariov0.8.10.9.0
Startup, 10,000 components (task ms)169.7169.3 with the 0.9 syntax, 169.7 with the 0.8 syntax
Mounted memory, 10,000 components16.7 MB16.9 MB
Retained after 100 × 500 mount and unmount0.456 MB0.444 MB
Idle for 10 s: script, flushes, timer ticks0, 0, 00, 0, 0
Script per click0.0349 ms0.0393 ms (0.9 syntax), 0.0378 ms (0.8 syntax)
Click → DOM text / attribute / focus0.4 / 0.4 / 0.5 ms0.3 / 0.3 / 0.5 ms
Form input, script per keystroke, bind: + text:0.0078 ms0.0080 ms
Compile, 5,000 native rules + 1 reactive rule24.5 ms26.1 ms
Compile, 500 event rules: on: / :on()4.6 ms / —4.8 ms / 6.2 ms

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) with event(value), bind(value) and content): 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.

ScenarioReactive CSS p50 / p95Plain JavaScript p50 / p95
Idle14.0 / 15.7 ms13.6 / 16.0 ms
Under load12.2 / 20.9 ms12.7 / 23.0 ms

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.

Bundle0.8.1 min / gzip / brotli0.9.0 min / gzip / brotli
reactive-css.min.js
default, with describe() descriptions
117,830 / 39,715 / 34,983149,177 / 49,278 / 43,307
reactive-css.compact.min.js
same runtime, shorter registry
101,644 / 34,357 / 30,418128,633 / 42,530 / 37,611

Tests

From the 0.8.1 preparation report (§6).

SuiteResult
Typecheckpass
Unit (Node, engine-independent)196 passed, including the 0.9 grammar tables, the event-time evaluator, persistence decoding, the diagnostic codes and an isolated build of each of the 15 features
Browser, Chromium + WebKit848 passed, 6 skipped (tests that need the Chromium DevTools protocol); includes the 0.9 integration components, drag, persistence, the CodePen-style and strict-CSP pages, and the unchanged 0.8.1 suites
Browser, Chromium with the CPU throttled 4×427 passed
Custom bundlesa build matrix (minimal state, counter, forms, focus, persistence, drag, sensors, the interactive profile, full): exact feature sets, left-out features physically absent, each bundle run under a strict Content-Security-Policy
Every example on the bundle the analyzer picks for it134 / 134: the application demos and the 16 API examples, Chromium and WebKit
Firefox, automatednot run: Playwright’s Firefox does not start on the test machine

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.

Experimental · syntax may change between minor versions