DEV Community

Cover image for šŸš€ State.js - Build Reactive, Interactive UIs Using Only HTML + CSS
iDev-Games
iDev-Games

Posted on

šŸš€ State.js - Build Reactive, Interactive UIs Using Only HTML + CSS

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%
Enter fullscreen mode Exit fullscreen mode

Then you use those variables in CSS:

.health-bar {
    width: var(--state-health-percent);
}
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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">
Enter fullscreen mode Exit fullscreen mode

A video progress bar:

video::after {
    width: var(--state-progress);
}
Enter fullscreen mode Exit fullscreen mode

A toggleable UI state:

<div data-state data-state-toggles="active" data-active="false"></div>
Enter fullscreen mode Exit fullscreen mode

🧩 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>
Enter fullscreen mode Exit fullscreen mode

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)

Collapse
 
p0rt profile image
Sergei Parfenov

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?

Collapse
 
idevgames profile image
iDev-Games

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.