Most frontend tools make you learn a framework, write components, manage state, and fight a build pipeline⦠just to update a number on the screen.
State.js flips that model on its head.
It turns HTML data attributes into a fully reactive UI engine - exposing your elementās state as CSS variables, updating automatically, and letting you build dynamic interfaces without you writing any JavaScript logic yourself.
If you know HTML and CSS, you already know how to use State.js.
⨠What State.js Actually Does (In Plain English)
State.js watches your HTML elements and automatically exposes their state as CSS variables:
data-health="75" ā --state-health: 75
data-health-max="100" ā --state-health-percent: 75%
Then you use those variables in CSS:
.health-bar {
width: var(--state-health-percent);
}
Change the attribute ā UI updates instantly ā CSS animates it.
No JS functions.
No components.
No re-renders.
No virtual DOM.
Just HTML + CSS.
š„ Why Developers Love It
1. You don't need to write any JavaScript Logic
You can build:
- dashboards
- progress bars
- form interactions
- clicker games
- idle games
- RPG UIs
- video players
- reactive animations
ā¦without writing a single line of JS.
2. Designers Can Work Directly in the Code
State.js exposes everything as:
- CSS variables
- data attributes
- conditional classes
Designers donāt need to touch JS files or understand framework lifecycles.
3. BackendāFirst Friendly
Works perfectly with:
- Laravel
- Rails
- WordPress
- Django
- Phoenix
- HTMX
No bundlers.
No hydration.
No clientāside routing.
Just reactive HTML.
4. GameāDev Ready
State.js includes:
- timers
- computed values
- sound effects
- persistence
- event dispatch
- interval triggers
- autoāfire logic
- conditional classes
You can build a full idle game in pure HTML.
ā” What You Can Build in 30 Seconds
A clicker game:
<div id="clicker" data-state data-score="0" data-state-watch="score">
<h1>Score: <span data-state-display="score"></span></h1>
<button data-state-trigger
data-state-bind="clicker"
data-state-attr="score"
data-state-increment="1">
Click Me!
</button>
</div>
A health bar:
<div data-state data-hp="75" data-hp-max="100" data-state-var="true">
<div class="bar" style="width: var(--state-hp-percent);"></div>
</div>
A form that tracks focus + changes:
<input data-state-trigger data-state-trigger-on="focus"
data-state-bind="form" data-state-attr="focusCount" data-state-increment="1">
A video progress bar:
video::after {
width: var(--state-progress);
}
A toggleable UI state:
<div data-state data-state-toggles="active" data-active="false"></div>
š§© The Magic Primitives (The 80% You Need to Know)
Here are the core building blocks that let you build anything:
1. data-state
Turns an element into a reactive state container.
2. data-state-watch="attr1,attr2"
Watches attributes and exposes them as CSS variables.
3. data-state-trigger
Makes an element update state when interacted with.
4. data-state-attr="health"
Which attribute to update.
5. data-state-increment="10"
Add to a value.
6. data-state-set="100"
Set a value exactly.
7. data-state-condition="gold >= 50"
Only fire if condition is true.
8. data-state-autofire="true"
Fire automatically when condition becomes true.
9. data-state-text="HP {hp}/{hpmax}"
Template string interpolation.
10. data-state-class="critical"
Conditional CSS classes.
11. data-state-interval="1000"
Run triggers every N ms (timers, regen, idle loops).
12. data-state-sound="coin"
Play procedural sound effects.
13. data-state-persist="true"
Save + restore state from localStorage.
14. data-state-trigger-on="input|scroll|mouseenter|submit"
Eventābased triggers with debounce/throttle.
15. data-state-include="component.html"
HTML includes ā reusable components with no build step.
š® Example: A Full Idle Game in Pure HTML
You can literally build this with no JS:
- gold counter
- click power
- upgrades
- level scaling
- passive income
- autoāleveling
- sound effects
- persistence
All declarative.
This is why State.js is exploding in gameādev circles.
šØ Example: A Fully Reactive UI Component
<div id="player"
data-state
data-state-watch="health,mana,xp"
data-state-var="true"
data-health="100"
data-mana="80"
data-xp="450">
<div class="health" style="width: var(--state-health-percent)"></div>
<div class="mana" style="width: var(--state-mana-percent)"></div>
<div class="xp" style="width: var(--state-xp-percent)"></div>
</div>
CSS handles the rest.
š§ Why This Works: The Mental Model
State.js is built on one simple idea:
HTML holds the data.
CSS reacts to the data.
State.js keeps them in sync.
Thatās it.
No components.
No framework.
No build step.
No hydration.
No virtual DOM.
Just declarative UI.
š Want to Go Deeper?
The README is your full reference manual ā every primitive, every example, every advanced feature.
But with what youāve learned here, you can already:
- build reactive dashboards
- build interactive UI
- build game interfaces
- build idle games
- build RPG UIs
- build form logic
- build video players
- build reusable components
All with HTML + CSS only.
View the repo here
Top comments (2)
the data-health ā --state-health-percent bridge is the nice part ā maps to how CSS already wants to work.
one honest nit: this isnt "zero JavaScript", its zero JS in your code. State.js is JS doing the attributeāvariable sync. which is the whole value ā better pitch is "the JS is written once in the library so you never touch it."
where does it stop being the right tool though? a health bar or clicker is a perfect fit. but once state has derived-values-of-derived-values or cross-field validation, declarative attributes start fighting you. is the line "past simple reactive display, reach for real JS" ā or are you pushing the computed primitives far enough to keep moving it?
Thanks, I've updated the article to replace "zero JavaScript Logic" in the post and better explain that you won't be writing JS yourself (out of the box).
The computed primitives are intentionally growing. The goal is to push declarative UI as far as it can reasonably go without turning HTML into a programming language. For anything deeply procedural or algorithmic, State.js works great alongside real JS, the idea is to avoid unnecessary overhead, not replace JavaScript entirely.
There are also dev tools that help you inspect reactive elements and understand the bigger picture of your architecture, so you can mix declarative and imperative approaches cleanly when you need to.