Arena by Dravensoft

One design system, in React and in Angular, from one contract.

One ArenaButton under
three style plugins, with the API and behaviour contracts pointing at it

Every value traces to a design token, every component's API and every accessibility pattern it binds is a contract file rather than a paragraph, and a gate holds the code, the documentation and the published packages to those contracts. An agent handed this system does not guess at it: it reads the contract that governs what it is about to write, and the gate tells it when it got it wrong.

Arena carries the language and not the skin. The same 100 components ship under both framework names, rendering the same pixels, and your palettes and fonts arrive in an arena.config.json your project writes.

bun add @dravensoft/arena-react     # or @dravensoft/arena-angular

See it

Web benches

Eight products, each mocked twice, once in React and once in Angular, from the same arena.config.json and the same design directory, so a difference between a pair is Arena answering the two layers differently. Each link names the bundler its half is built with, which moves with every run of the bundler measurements.

Calendly

ClickUp

Duolingo

Etsy

Grafana

Instagram

Notion

Superhuman

Built against Arena 13.0.0. The repository.

What each bundler measured

Arena does not depend on a bundler: any bundler that resolves a package's exports builds an app with it that works. Each bundler below was measured, and on those the config of its page keeps Arena's JavaScript and its package sheet in files of their own, so a release of the app downloads Arena again only where that row says so. Figures are KB of 1024 bytes, brotli at quality 11, written React / Angular; "x to y" is the build before and after the page's config.

bundlerversionhalfkeeps Arena apartfirst visitapp release onlyone-component patchwith a dictionary
rollup4.65.0calendlyyes, manualChunks57.85 to 59 / 257.45 to 258.6957.94 to 39.79 / 257.47 to 6.4558.96 / 258.560.36 / 0.08
Vite 77.3.7etsyyes, manualChunks59.62 to 60.01 / 116.89 to 119.0859.53 to 5.14 / 116.89 to 9.5121.23 / 119.040.09 / 0.09
Vite 88.3.5notionyes, codeSplitting.groups63.46 to 64.54 / 89.12 to 90.9163.44 to 5.81 / 89.13 to 11.6426.05 / 90.920.09 / 0.09
rolldown1.2.15grafana (previous run)yes, codeSplitting.groups70.42 to 71.99 / 255.95 to 257.970.34 to 40 / 255.76 to 10.8771.66 / 257.950.09 / 0.09
webpack5.112.2instagramyes, splitChunks.cacheGroups57.88 to 59.49 / 257.78 to 267.5257.86 to 5.56 / 257.81 to 157.8621.31 / 267.680.49 / 0.09
rspack2.2.9superhumanyes, splitChunks.cacheGroups54.93 to 56.62 / 74.24 to 80.4454.86 to 4.01 / 73.87 to 60.5618.46 / 80.120.5 / 0.23
Parcel2.16.4superhuman (previous run)yes, manualSharedBundles, and packageExports: true55.66 to 58.38 / 278.26 to 284.9855.64 to 42.43 / 278.47 to 174.258.39 / 285.04not measured: no name stem pairs a file with its predecessor
@angular/build application22.2.4grafananonone / 98.5none / 98.39none / 98.47none / 0.05
Next.js (Turbopack)16.4.3grafanano: no chunk holds Arena alone180.06 / none32.52 / none32.51 / nonenot measured: no name stem pairs a file with its predecessor
Bun bun build1.4.0duolingono93.46 / 274.1493.53 / 274.1193.55 / 2741.35 / 0.05
esbuild0.28.2clickupno75.24 / 199.8475.26 / 199.875.22 / 199.750.05 / 0.05

How each row was measured, and the config each bundler's page gives.

Take it