Arena by Dravensoft
One design system, in React and in Angular, from one contract.
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
- Overview
- Identity
- Guidelines
- Kitchen sink, react
- Kitchen sink, angular
- Playgrounds, react
- Playgrounds, angular
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
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.
| bundler | version | half | keeps Arena apart | first visit | app release only | one-component patch | with a dictionary |
|---|---|---|---|---|---|---|---|
| rollup | 4.65.0 | calendly | yes, manualChunks | 57.85 to 59 / 257.45 to 258.69 | 57.94 to 39.79 / 257.47 to 6.45 | 58.96 / 258.56 | 0.36 / 0.08 |
| Vite 7 | 7.3.7 | etsy | yes, manualChunks | 59.62 to 60.01 / 116.89 to 119.08 | 59.53 to 5.14 / 116.89 to 9.51 | 21.23 / 119.04 | 0.09 / 0.09 |
| Vite 8 | 8.3.5 | notion | yes, codeSplitting.groups | 63.46 to 64.54 / 89.12 to 90.91 | 63.44 to 5.81 / 89.13 to 11.64 | 26.05 / 90.92 | 0.09 / 0.09 |
| rolldown | 1.2.15 | grafana (previous run) | yes, codeSplitting.groups | 70.42 to 71.99 / 255.95 to 257.9 | 70.34 to 40 / 255.76 to 10.87 | 71.66 / 257.95 | 0.09 / 0.09 |
| webpack | 5.112.2 | yes, splitChunks.cacheGroups | 57.88 to 59.49 / 257.78 to 267.52 | 57.86 to 5.56 / 257.81 to 157.86 | 21.31 / 267.68 | 0.49 / 0.09 | |
| rspack | 2.2.9 | superhuman | yes, splitChunks.cacheGroups | 54.93 to 56.62 / 74.24 to 80.44 | 54.86 to 4.01 / 73.87 to 60.56 | 18.46 / 80.12 | 0.5 / 0.23 |
| Parcel | 2.16.4 | superhuman (previous run) | yes, manualSharedBundles, and packageExports: true | 55.66 to 58.38 / 278.26 to 284.98 | 55.64 to 42.43 / 278.47 to 174.2 | 58.39 / 285.04 | not measured: no name stem pairs a file with its predecessor |
@angular/build application | 22.2.4 | grafana | no | none / 98.5 | none / 98.39 | none / 98.47 | none / 0.05 |
| Next.js (Turbopack) | 16.4.3 | grafana | no: no chunk holds Arena alone | 180.06 / none | 32.52 / none | 32.51 / none | not measured: no name stem pairs a file with its predecessor |
Bun bun build | 1.4.0 | duolingo | no | 93.46 / 274.14 | 93.53 / 274.11 | 93.55 / 274 | 1.35 / 0.05 |
| esbuild | 0.28.2 | clickup | no | 75.24 / 199.84 | 75.26 / 199.8 | 75.22 / 199.75 | 0.05 / 0.05 |
How each row was measured, and the config each bundler's page gives.