DEV Community

Cover image for State.js Tutorial: Creating Reusable UI Components with Pure CSS Reactivity
iDev-Games
iDev-Games

Posted on • Edited on

State.js Tutorial: Creating Reusable UI Components with Pure CSS Reactivity

Modern frontend frameworks treat components as JavaScript functions, classes, or compiled files.

But what if components didn’t need JavaScript at all?

What if components were built from:

  • HTML templates
  • CSS for behavior and visuals
  • State.js attributes for reactivity

…with no JS logic, no virtual DOM, and no build step?

That’s exactly what State.js enables.

State.js turns HTML attributes into reactive CSS variables, letting you build fully reactive, reusable UI components using nothing but HTML + CSS.

In this article, you’ll learn how State.js components actually work.


What Is a Component in State.js?

In React, a component is a function.

In Vue, it’s an object.

In Svelte, it’s a compiled file.

In State.js, a component is:

A reusable HTML template that becomes reactive when included with data-state-include.

A component is:

  • defined once
  • cloned anywhere
  • customized via attributes
  • reactive automatically
  • fully encapsulated

No JavaScript logic.

No rendering engine.

No framework runtime.

Just HTML + CSS + State.js.


Example: A Reusable <char-card> Component

1. Define the component once

<template id="char-card">
  <div class="char-card" data-state data-hp="20" data-hp-max="20">
    <div class="name" data-state-display="name"></div>

    <div class="bar">
      <div class="fill" style="width: var(--state-hp-percent)"></div>
    </div>

    <button data-state-trigger data-state-attr="hp" data-state-increment="-5">
      Damage
    </button>
  </div>
</template>
Enter fullscreen mode Exit fullscreen mode

This is the component definition.

State.js automatically exposes:

  • --state-hp
  • --state-hp-max
  • --state-hp-percent

These update reactively whenever attributes change.


2. Use the component anywhere

<div data-state-include="#char-card"
     data-name="Warrior"
     data-hp="18"
     data-hp-max="20"></div>

<div data-state-include="#char-card"
     data-name="Mage"
     data-hp="9"
     data-hp-max="12"></div>
Enter fullscreen mode Exit fullscreen mode

Each instance:

  • clones the template
  • merges your attributes
  • becomes a fully reactive component
  • auto-binds triggers to its own state

This is the State.js equivalent of props, but simpler.


CSS Drives the Component’s Behavior

.char-card .bar {
  height: 12px;
  background: #333;
  border-radius: 4px;
  overflow: hidden;
}

.char-card .fill {
  height: 100%;
  background: #4caf50;
  width: var(--state-hp-percent);
  transition: width 0.3s ease;
}
Enter fullscreen mode Exit fullscreen mode

CSS handles:

  • animation
  • interpolation
  • transitions
  • layout

State.js handles:

  • state changes
  • variable updates
  • triggers
  • reactivity

This is CSS‑reactive architecture.


Updating the Component

Inside the component:

<button data-state-trigger data-state-attr="hp" data-state-increment="-5">
  Damage
</button>
Enter fullscreen mode Exit fullscreen mode

When clicked:

  • data-hp decreases
  • State.js updates --state-hp
  • CSS recalculates --state-hp-percent
  • The bar animates automatically

This is reactivity without JavaScript logic.


Why This Works

State.js turns HTML attributes into live CSS variables.

CSS handles:

  • layout
  • animation
  • transitions
  • transforms

State.js handles:

  • state
  • triggers
  • conditions
  • intervals
  • reactivity

Together, they form a browser‑native component system.


Why This Is a Big Deal

✔ Zero JavaScript logic

Your component logic lives in HTML + CSS.

✔ Zero framework runtime

No virtual DOM.

No hydration.

No re-renders.

✔ Zero build step

Works in plain HTML files.

✔ Fully portable components

A <char-card> works anywhere HTML works.

✔ Native browser performance

CSS is GPU‑accelerated and instant.

✔ Auto‑binding triggers

Buttons inside the template automatically bind to the nearest [data-state] parent.


Component API Design

You can define your own “props” using attributes:

<div data-state-include="#char-card"
     data-name="Rogue"
     data-hp="12"
     data-hp-max="14"></div>
Enter fullscreen mode Exit fullscreen mode

Or add more:

<div data-state-include="#char-card"
     data-name="Cleric"
     data-hp="14"
     data-hp-max="16"
     data-color="blue"
     data-low="red"></div>
Enter fullscreen mode Exit fullscreen mode

State.js exposes them all as CSS variables.


Conclusion

State.js lets you build components using:

  • HTML for structure
  • CSS for behavior
  • State.js for reactivity

This creates a new category of UI architecture:

CSS‑Reactive Components

Components powered by HTML templates, CSS variables, and declarative reactivity — not JavaScript logic.

If you want lightweight, portable, framework‑free UI, this pattern is a game changer.

Top comments (0)