Compile-time
No Proxy, no Virtual DOM. The compiler emits surgical DOM updates — it knows exactly which node depends on which value.
Reactivity is resolved at compile time. Components are native Web Components with their own Shadow DOM. Deep reactivity by default, and SSR included.
No Proxy, no Virtual DOM. The compiler emits surgical DOM updates — it knows exactly which node depends on which value.
0.98× Svelte 5 on the official benchmark, ~17 KB brotli (~19 KB gzip), and ~15 ms for 1M deep mutations. No runtime Proxy overhead.
Each component is a Custom Element with its own Shadow DOM. No proprietary runtime — just the platform.
Nested objects and arrays track automatically.
Render-then-replace, Declarative Shadow DOM.
Built-in transitions and FLIP animations.
A client-side router, batteries included.
Global state with $$ stores and § context.
<@module> mounts a component by name.
Reusable markup fragments and named slots.
<@failed> catches errors and shows a fallback.
This counter is a live MJS component. Reactivity is compiled — clicking updates only the number, nothing else.
<script> $count = 0 inc = -> $count++ </script> <button @click={inc}> Clicks : {$count} </button>
Every example below is live: click, tick, drag. The code beside it is the code that runs.
A list, checkboxes, a total: you change one piece of data, the screen follows. Nothing to rewire, ever.
{for todo in $todos}
<@checkbox checked=!{todo}.done>{todo.label}</@checkbox>
{end}
<p class="count">{$doneCount} / {$todos.length}</p>0 of 3 done
Two dollar signs in front of a name, and the value exists for the whole page. Three cards, a single score, no wire to pull.
<button @click={$$score++}>{$$score}</button>0
shared score
0
shared score
0
shared score
A letter that flies, a stagger between letters, a gradient that slides: it all fits in one attribute set on the tag. Click to replay.
<script>
$k = 0
letters = 'ModularJS'.split ''
replay = -> $k++
</script>
{key $k}
<h1 class="title">
{for i, l in letters}
<span @in.fly={ y: 40, delay: i * 60, duration: 600, easing: µ.easing.backOut }>{l}</span>
{end}
</h1>
{end}
<button @click={replay()}>Replay</button>Thirteen ways to move from one screen to another, adjustable with a single word. Choose a preset, a direction, and watch the page turn.
<routes target="vt">
</routes>
<@view vt @viewTransition.cube={ dir: left }>Searchable dropdown, checkboxes, radio buttons, toggle switch, color swatch, optimized image: shipped with the framework, keyboard-operable, and they submit through an ordinary form.
<div class="screen" --accent={$color}>
<@field label="Nickname" help="Visible to everyone">
<input type="text" name="nick" value=!{$nick} aria-label="Nickname">
</@field>
<@select name="country" search placeholder="Country…" search-placeholder="Search…" value=!{$country}>
<@option value="fr">🇫🇷 France</@option>
<@option value="be">🇧🇪 Belgium</@option>
<@option value="ch">🇨🇭 Switzerland</@option>
<@option value="ca">🇨🇦 Canada</@option>
<@option value="de">🇩🇪 Germany</@option>
<@option value="es">🇪🇸 Spain</@option>
<@option value="it">🇮🇹 Italy</@option>
<@option value="gb">🇬🇧 United Kingdom</@option>
<@option value="us">🇺🇸 United States</@option>
<@option value="jp">🇯🇵 Japan</@option>
</@select>
<div class="row">
<@radio name="size" value="s" group=!{$size}>S</@radio>
<@radio name="size" value="m" group=!{$size}>M</@radio>
<@radio name="size" value="l" group=!{$size}>L</@radio>
</div>
<div class="row">
<@checkbox name="news" checked=!{$news}>Receive updates</@checkbox>
<@switch name="night" checked=!{$night}>Night mode</@switch>
</div>
<div class="row">
<@color value=!{$color} editable label="Profile color">
<@img src="tuto/girl.jpg" alt="Avatar" widths="96 192" sizes="48px">
</div>
<p class="summary">{$nick} · {$country} · size {$size.toUpperCase()} · {if $night}night{else}day{end} · {if $news}subscribed{else}not subscribed{end}</p>
</div>Visible to everyone

Léa · 🇫🇷 France · size M · night · subscribed
Delete, send, leave: ask the question right in the tag. A dialog box in your own colors opens, and nothing happens until someone says yes. The same box can also ask for text, or announce a success.
<button class="x" @confirm="Delete this photo?" @click={remove(photo.id)} aria-label="Delete">✖</button>result = await µmodal.fire title: 'Your first name?', input: 'text'



A success, a warning, a message: one line, and a notification slides into the corner of the screen with its health bar, Steam-style. Sound is added with a single word.
µmodal.notify 'Achievement unlocked: first notification', type: 'success', sound: truebottom right, with sound
Colors live apart from the code: a block of values, set on a card, a section, or the whole site. The closest one wins, and everything inside follows — nested components included.
<theme name="gold">
$$accent: #d4af37
$$card-surface: #2a2410
</theme><@showcase-card theme="gold">Léa
Designer
Marc
Developer
Nour
Project lead
You
Visitor
one attribute, nothing else
Declare your screens, place a zone: links change the view without a reload, the address bar follows, and the Back button works.
<routes target="app">
/ showcase-page-home
/photos showcase-page-photos
/profil showcase-page-profile
/* showcase-page-home
</routes>
<div class="screen">
<div class="phone">
<nav class="tabs">
<a href="#/" @class{µurl.path == '/'}="on">Home</a>
<a href="#/photos" @class{µurl.path == '/photos'}="on">Photos</a>
<a href="#/profil" @class{µurl.path == '/profil'}="on">Profile</a>
</nav>
<div class="view">
<@view app @viewTransition.none>
</div>
</div>
<p class="hint">click: the address changes, the page does not reload</p>
</div>
<style>
:host
display: block
width: 100%
.screen
display: flex
flex-direction: column
align-items: center
justify-content: center
gap: 16px
width: 100%
height: 300px
border-radius: 14px
background: $$screen
box-shadow: inset 0 0 40px color-mix(in srgb, $$accent 8%, transparent)
.phone
width: 260px
margin: 0 auto
border-radius: 22px
border: 1px solid $$border
background: $$panel
overflow: hidden
.tabs
display: flex
a
flex: 1
text-align: center
padding: 10px 0
color: $$muted
text-decoration: none
font-size: 13px
&.on
color: $$fg
box-shadow: inset 0 -2px 0 $$accent
.view
height: 220px
padding: 14px
margin-top: 4px
overflow: hidden
position: relative
.hint
margin: 0
color: $$muted
font-size: 13px
</style><h3>Hello!</h3>
<p>Three screens, one routes file.</p>
<style>
:host
display: block
padding: 16px
color: $$fg
animation: enter .35s ease-out
h3
margin: 0 0 8px
color: $$accent
p
color: $$muted
font-size: 13px
@keyframes enter
from
opacity: 0
transform: translateX(24px)
to
opacity: 1
transform: none
</style><h3>Photos</h3>
<div class="grid">
<img src="µasset('tuto/16/16-5/retrowave.webp')" alt="">
<img src="µasset('tuto/16/16-2/deepsea.webp')" alt="">
<img src="µasset('tuto/16/16-5/halloween.webp')" alt="">
<img src="µasset('tuto/16/16-5/spaaaaace.webp')" alt="">
</div>
<style>
:host
display: block
padding: 16px
color: $$fg
animation: enter .35s ease-out
h3
margin: 0 0 8px
color: $$accent
.grid
display: grid
grid-template-columns: 1fr 1fr
gap: 8px
img
width: 100%
aspect-ratio: 4 / 3
object-fit: cover
border-radius: 8px
@keyframes enter
from
opacity: 0
transform: translateX(24px)
to
opacity: 1
transform: none
</style><h3>Profile</h3>
<div class="avatar">L</div>
<p>Léa — Designer</p>
<style>
:host
display: block
padding: 16px
color: $$fg
animation: enter .35s ease-out
h3
margin: 0 0 8px
color: $$accent
p
color: $$muted
font-size: 13px
.avatar
display: flex
align-items: center
justify-content: center
width: 48px
height: 48px
margin: 8px 0
border-radius: 50%
background: $$accent
color: #06111f
font-weight: 700
font-size: 18px
@keyframes enter
from
opacity: 0
transform: translateX(24px)
to
opacity: 1
transform: none
</style>| Criterion | MJS | Svelte | React | Solid |
|---|---|---|---|---|
| Approach | compile-time | compile-time | VDOM | compile-time |
| Runtime | ~17 KB | ~10 KB | ~45 KB | ~7 KB |
| Deep reactivity, no Proxy | ✓ | partial | no | manual |
| Native Web Components | ✓ | no | no | no |
| SSR | ✓ | ✓ | ✓ | ✓ |
Figures are indicative and meant to be honest, not promotional.
Audited by Fable 5.1 (Claude, Anthropic)
Reference mega-benchmark: the same weather application — city search, forecasts, persistence, accessibility — written in 14 frameworks, rendered client-side with no prerendering, like the compiled frameworks.
The 14 versions measured on the evening of 09/18/2026, same machine, same session: first visit then second visit
The MJS version inlines only the critical CSS into its index.html and defers the rest to a linked style sheet, as the Angular build does for its own; the other versions serve their style sheets as the benchmark provides them. The benchmark does not count index.html in the weight.
Audited by Fable 5.1 (Claude, Anthropic)
One command to get the framework.
npm install modularjs-framework