DEV Community

Cover image for ⭐ State.js Basics — Learn CSS‑Driven Reactivity in 10 Minutes
iDev-Games
iDev-Games

Posted on

⭐ State.js Basics — Learn CSS‑Driven Reactivity in 10 Minutes

State.js looks simple — just HTML attributes — but it introduces a new mental model for building UI:

HTML holds the data.

CSS reacts to the data.

State.js keeps them in sync.

If you’re used to React, Vue, or Svelte, this feels strange at first.

If you’re used to CSS, this feels like “why didn’t CSS always work like this?”

This tutorial will teach you the core ideas behind State.js so you can build reactive UI without JavaScript logic, without a framework, and without a build step.


⭐ 1. What State.js Actually Does

State.js turns HTML attributes into live CSS variables.

Example:

<div data-state data-count="0"></div>
Enter fullscreen mode Exit fullscreen mode

State.js automatically exposes:

--state-count: 0;
Enter fullscreen mode Exit fullscreen mode

If data-count changes, the CSS variable updates instantly.

This is the foundation of everything.

Learn more: Reactive attributes


⭐ 2. Your First Reactive Element

Let’s make a simple counter.

HTML

<div id="counter"
     data-state
     data-count="0"
     data-state-text="Count: {count}">
</div>
Enter fullscreen mode Exit fullscreen mode

What’s happening?

  • data-state → activates State.js
  • data-count="0" → creates --state-count: 0
  • data-state-text="Count: {count}" → binds text to the value

State.js replaces {count} with the live value.


⭐ 3. Updating State with Triggers

Add a button that increments the counter:

<button
  data-state-trigger
  data-state-target="#counter"
  data-state-attr="count"
  data-state-increment="1">
  +1
</button>
Enter fullscreen mode Exit fullscreen mode

What this means:

  • data-state-trigger → this element performs an action
  • data-state-target="#counter" → update that element
  • data-state-attr="count" → update the data-count attribute
  • data-state-increment="1" → add 1

Clicking the button updates:

  • the attribute
  • the CSS variable
  • the text
  • all automatically

Learn more: Triggers


⭐ 4. Styling with CSS Variables

Because State.js exposes attributes as CSS variables, you can style reactively:

#counter {
  color: hsl(calc(var(--state-count) * 20), 80%, 50%);
}
Enter fullscreen mode Exit fullscreen mode

As the count increases, the color changes.

No JS.

No re-renders.

Just CSS reacting to state.

Learn more: CSS variable projection


⭐ 5. Conditions (Reactive Logic in HTML)

Let’s change the background when the count reaches 5.

<div id="counter"
     data-state
     data-count="0"
     data-state-class="big: count >= 5"
     data-state-text="Count: {count}">
</div>
Enter fullscreen mode Exit fullscreen mode

This adds the class .big when the condition is true.

CSS:

.big {
  background: gold;
}
Enter fullscreen mode Exit fullscreen mode

Learn more: Conditions


⭐ 6. Autofire (State.js “Game Loop”)

State.js can update values on an interval:

<div data-state
     data-time="0"
     data-state-autofire="time += 1 every 100ms"
     data-state-text="Time: {time}">
</div>
Enter fullscreen mode Exit fullscreen mode

This creates a reactive timer with no JavaScript.

Learn more: Intervals


⭐ 7. Two-Way Binding (Forms Without JS)

<input type="range"
       data-state
       data-value="50"
       data-state-bind="value">

<div data-state
     data-state-text="Value: {value}">
</div>
Enter fullscreen mode Exit fullscreen mode

Moving the slider updates the text automatically.

Learn more: Binding


⭐ 8. Putting It All Together — A Mini App

<div id="app" data-state data-count="0">
  <h1 data-state-text="Count: {count}"></h1>

  <button
    data-state-trigger
    data-state-target="#app"
    data-state-attr="count"
    data-state-increment="1">
    +1
  </button>

  <button
    data-state-trigger
    data-state-target="#app"
    data-state-attr="count"
    data-state-increment="-1">
    -1
  </button>

  <div data-state-class="warning: count < 0">
    <p>Count is negative!</p>
  </div>
</div>
Enter fullscreen mode Exit fullscreen mode

What you get:

  • reactive text
  • reactive classes
  • reactive styling
  • reactive state
  • no JavaScript logic

This is the State.js mental model.


⭐ 9. Why This Matters

State.js gives you:

✔ Reactivity without JavaScript

No functions.

No hooks.

No re-renders.

✔ Components without frameworks

Just templates + includes.

✔ State without state management libraries

Attributes are the state.

✔ UI logic without JS

CSS handles behavior.

✔ Zero build step

Works in plain HTML.

This is why people say:

“It feels like something CSS should have done.”

Because State.js makes the browser behave the way developers wish it behaved.

Top comments (0)