State.js


Documentation v1.6.2


What is State.js?


State.js is a super simple, efficient and lightweight CSS framework that exposes DOM element states as CSS variables. Track data attributes, form inputs, media playback, and element visibility - all automatically exposed for use in your CSS animations and transitions.
Using nothing but the power of CSS, HTML and State.js, you can create dynamic dashboards, reactive web applications, and interactive experiences! State.js is really lightweight and created with javascript without requiring any dependencies.
State.js includes state-animations.css with predefined UI animations. Pair with Cursor.js, Keys.js, and Motion.js for a complete CSS/HTML development toolkit - perfect for games, dashboards, and interactive interfaces.

State-Animations.css


Predefined CSS animations for UI feedback, notifications, progress indicators, status effects, and interactive elements

How To Install?

All you need to do is add the state.js file into your projects JS folder and add the following code with your state.js location as the src. Put this code in to your head HTML tags.

                            <script src="/js/state.js"></script>
                    

Or just add a CDN instead (coming soon)

                        <script src="https://cdn.jsdelivr.net/npm/@idevgames/state-js/src/state.js"></script>
                    

How To Use?

To activate state.js add the data attribute "data-state" or a class "enable-state" to your html element. State.js automatically tracks element visibility and adds the "state" and "state-visible" classes when elements appear on screen.

                            <div class="fadeIn" data-state> </div>
                    
                        .fadeIn{ opacity:0; }
                        .fadeIn.state{ animation: fadeIn 1s normal forwards ease-in-out; }
                        @keyframes fadeIn { 0% { opacity:0; } 100% { opacity:1; } }
                    

Data Attribute Tracking

State.js can watch data attributes and automatically expose them as CSS variables. Perfect for progress indicators, counters, stats, and dynamic data visualization!

                            <div id="player" data-state
                                 data-state-watch="health,score"
                                 data-health="100"
                                 data-health-min="0"
                                 data-health-max="100"
                                 data-score="0">
                            </div>
                    
                            #player .health-bar {
                                width: var(--state-health-percent);
                                background: linear-gradient(90deg, red, yellow, green);
                            }

                            [data-health="0"] {
                                animation: death 2s forwards;
                            }
                    

Interactive Demo

Move the slider to change the health value (no JavaScript needed!):

Health: 75%

The Code Behind This Demo:

<!-- Slider automatically updates healthDemo element -->
<input type="range"
       data-state
       data-state-bind="healthDemo"
       data-state-attr="health"
       min="0" max="100" value="75">

<!-- Health bar watches the health attribute -->
<div id="healthDemo"
     data-state
     data-state-watch="health"
     data-health="75"
     data-health-min="0"
     data-health-max="100">

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

    <div>Health: <span data-state-display="health">75</span>%</div>
</div>

Button Triggers & Increment

Make buttons control state without writing JavaScript! Use increment for clickers and counters:

Clicks: 0

The Code:

<div id="clickerDemo"
     data-state
     data-state-watch="clicks"
     data-clicks="0">

    <div>Clicks: <span data-state-display="clicks">0</span></div>

    <!-- Button increments by 1 -->
    <button data-state
            data-state-trigger
            data-state-bind="clickerDemo"
            data-state-attr="clicks"
            data-state-increment="1">
        CLICK ME +1
    </button>

    <!-- Button increments by 5 -->
    <button data-state
            data-state-trigger
            data-state-bind="clickerDemo"
            data-state-attr="clicks"
            data-state-increment="5">
        CLICK ME +5
    </button>

    <!-- Progress bar using CSS variable -->
    <div class="progress-bar"
         style="width: var(--state-clicks-percent);"></div>
</div>

Auto-firing Triggers: The Missing Primitive

Automatically fire triggers when conditions become true - perfect for passive income, auto-unlocks, achievements, and automatic progression!

Gold: 0
Gems: 0
Status: Locked (need 20 gold)
What's happening:
  • Every 10 gold automatically converts to 1 gem (passive income!)
  • When you reach 20 gold, status automatically unlocks
  • No clicks required - pure automatic progression!

The Code:

<!-- Passive income: auto-convert 10 gold → 1 gem -->
<button id="autoConvert"
        data-state
        data-state-trigger
        data-state-bind="player"
        data-state-attr="gold"
        data-state-decrement="10"
        data-state-condition="gold >= 10"
        data-state-autofire="true"
        data-state-trigger-chain="addGem"
        style="display:none">
</button>

<!-- Auto-unlock at level 20 -->
<button data-state
        data-state-trigger
        data-state-bind="player"
        data-state-attr="unlocked"
        data-state-set="true"
        data-state-condition="gold >= 20"
        data-state-autofire="true"
        style="display:none">
</button>

The Magic:

When a condition transitions from falsetrue, the trigger fires automatically! No click required. No visibility required. This is the missing primitive for automatic game mechanics.


New in v1.1.0: Game Development Extensions

Seven new declarative primitives for building complete games with ZERO hand-written JavaScript logic!


Extension 1: Interval Triggers (Passive Income)

Triggers that fire automatically on a timer - perfect for passive income and cooldowns!

Gold: 0
Workers: 0
Watch the magic:

Each worker generates 1 gold per second automatically! Hire more workers to increase your income.

The Code:

<!-- Hidden trigger fires every 1000ms -->
<button data-state
        data-state-trigger
        data-state-bind="player"
        data-state-attr="gold"
        data-state-increment="calc(var(--state-workers))"
        data-state-interval="1000"
        data-state-condition="workers > 0"
        style="display:none">
</button>

Extension 2 & 3: data-state-set & data-state-text

data-state-set sets exact values, data-state-text uses template strings!

Notice:

Text updates automatically using {token} syntax! Full Heal uses data-state-set with calc() to restore to max.

The Code:

<!-- Template string with tokens -->
<h2 data-state
    data-state-bind="player"
    data-state-text="Level {level} {name}">
</h2>

<!-- Set to exact value using calc() -->
<button data-state-trigger
        data-state-bind="player"
        data-state-attr="health"
        data-state-set="calc(var(--state-healthmax))">
    Full Heal
</button>

Extension 4: Conditional CSS Classes

Automatically add/remove CSS classes based on conditions - perfect for visual feedback!

Health: 100/100
Watch the bar change:

≤50 HP: Yellow pulse | ≤20 HP: Red shake | 100 HP: Green glow

The Code:

<div data-state
     data-state-bind="player"
     data-state-class="health-low"
     data-state-class-condition="health <= 50"
     data-state-class-2="health-critical"
     data-state-class-condition-2="health <= 20"
     data-state-class-3="health-full"
     data-state-class-condition-3="health >= 100">
</div>

<style>
.health-low { animation: pulse 1s infinite; }
.health-critical { animation: shake 0.5s infinite; }
.health-full { box-shadow: 0 0 20px green; }
</style>

Extension 5: Procedural Sound Effects

Fire-and-forget Web Audio sounds - no audio files needed! Click the buttons to hear:

Score: 0
Built-in sounds:

click • coin • buy • levelup

Generated with Web Audio API - zero external dependencies!

The Code:

<button data-state-trigger
        data-state-bind="player"
        data-state-attr="score"
        data-state-increment="10"
        data-state-sound="coin">
    Collect Coin
</button>

Extension 5.5: Event-Based Triggers (v1.4.0)

Triggers can now fire on ANY DOM event - not just clicks! Includes debounce/throttle for performance.

Hovers
0
Inputs
0
Focuses
0
Scrolls
0
Hover Me!

Fires on mouseenter event

Scroll This Area!

Throttled to 200ms - fires at most 5x per second

Keep scrolling...

Almost there...

Supported Events:

clickmouseentermouseleavefocusblurinputchangesubmitkeydownkeyupscrollintersect (visibility) • and more!

The Code:

<!-- Hover trigger -->
<div data-state-trigger
     data-state-trigger-on="mouseenter"
     data-state-bind="stats"
     data-state-attr="hovers"
     data-state-increment="1">
    Hover me!
</div>

<!-- Debounced input (500ms delay) -->
<input data-state-trigger
       data-state-trigger-on="input"
       data-state-debounce="500"
       data-state-bind="stats"
       data-state-attr="searches"
       data-state-increment="1">

<!-- Throttled scroll (max 5x/sec) -->
<div data-state-trigger
     data-state-trigger-on="scroll"
     data-state-throttle="200"
     data-state-bind="stats"
     data-state-attr="scrolls"
     data-state-increment="1">
    Scroll area
</div>

<!-- Form submit with preventDefault -->
<form data-state-trigger
      data-state-trigger-on="submit"
      data-state-bind="stats"
      data-state-attr="submits"
      data-state-increment="1">
    <!-- Automatically prevents page reload -->
</form>

<!-- Visibility detection -->
<div data-state-trigger
     data-state-trigger-on="intersect"
     data-state-bind="stats"
     data-state-attr="views"
     data-state-increment="1">
    Fires when scrolled into view
</div>
New in v1.4.0:
  • data-state-trigger-on - Listen to any DOM event (default: "click")
  • data-state-debounce - Delay execution until events stop (in ms)
  • data-state-throttle - Limit execution rate (in ms)
  • Form submit events automatically call preventDefault()
  • Custom intersect event fires when element enters viewport

Extension 6 & 7: Persist & Events

data-state-persist saves to localStorage, data-state-event dispatches CustomEvents!

Save Count: 0
Event log will appear here...
Try this:

1. Click "Increment & Save" a few times
2. Reload the page - your count persists!
3. Watch the event log below for CustomEvents

The Code:

<div id="game"
     data-state
     data-state-watch="score,level"
     data-state-persist="true"
     data-state-persist-key="my-game">
</div>

<button data-state-trigger
        data-state-attr="score"
        data-state-increment="10"
        data-state-event="score-up">
</button>

<script>
document.addEventListener('state:score-up', (e) => {
    console.log('Score increased!', e.detail);
});
</script>

New in v1.2.0: HTML Includes

data-state-include enables modular, reusable HTML components - just like other frameworks, but with zero build tools!

Why HTML Includes?

Build reusable components like you would in any modern framework. Create a health bar once, use it everywhere. No copy-pasting HTML, no build step, no complexity - just fetch and inject declaratively.

Basic Usage:

<!-- From external file (cached after first load) -->
<div data-state-include="components/health-bar.html"></div>

<!-- From inline template (instant, zero latency) -->
<div data-state-include="#health-bar-template"></div>

<!-- Override component attributes -->
<div data-state-include="components/health-bar.html"
     id="player-health"
     data-hp="75"
     data-hp-max="150"></div>

Example Components:

<!-- Option 1: External File (for modularity) -->
<!-- components/health-bar.html -->
<div class="health-bar"
     data-state
     data-state-watch="hp"
     data-state-var="true"
     data-hp="100"
     data-hp-max="100">
    <div class="fill"
         style="width: var(--state-hp-percent);
                background: green;
                height: 20px;"></div>
    <span data-state-display="hp"></span>
</div>

<!-- Option 2: Inline Template (for performance) -->
<template id="health-bar-template">
    <div class="health-bar"
         data-state
         data-state-watch="hp"
         data-state-var="true"
         data-hp="100"
         data-hp-max="100">
        <div class="fill"
             style="width: var(--state-hp-percent);
                    background: green;
                    height: 20px;"></div>
        <span data-state-display="hp"></span>
    </div>
</template>

<!-- Use it anywhere (instant!) -->
<div data-state-include="#health-bar-template" data-hp="75"></div>

How It Works:

  • Template Mode (#id): Instant cloning from <template> tag (zero latency)
  • Fetch Mode (path.html): Fetches from URL, cached after first load
  • Merges attributes from include element to component
  • Replaces the include element with the component
  • All State.js features work in included components!
Performance Strategy:

Use templates (#id) for critical, frequently-used components (instant, zero latency). Use files (path.html) for large component libraries across multiple pages (modularity). Template-based includes work anywhere, even with file:// protocol!


New in v1.3.0: Computed State

data-state-compute automatically calculates derived values from your data - perfect for percentages, damage formulas, and game logic!

Interactive Demo:

HP: 75 / 100 (75%)
Attack: 20
Defense: 10
Computed Damage: 30
Status: Alive

Code Example:

<div data-state
     data-state-watch="hp,maxHp,attack,defense"
     data-state-compute="
         hpPercent = hp / maxHp * 100;
         damage = (attack * 2) - defense;
         status = hp > 0 ? 'Alive' : 'Dead'
     "
     data-hp="75"
     data-maxHp="100"
     data-attack="20"
     data-defense="10">

    <!-- HP bar width automatically updates -->
    <div style="width: var(--state-hpPercent);"></div>

    <!-- Display computed values -->
    <span data-state-display="hpPercent"></span>%
    <span data-state-display="damage"></span>
    <span data-state-display="status"></span>
</div>

Supported Expressions:

  • Math: +, -, *, /, %, ()
  • Comparisons: <, >, <=, >=, ==, !=
  • Logical: &&, ||, !
  • Ternary: condition ? valueA : valueB
  • References: Use attribute names directly
Pro Tip:

Use computed state for health percentages, damage calculations, level-up requirements, resource management - any derived value that needs to stay in sync with its dependencies!

Debug API (v1.3.0):

Open your browser console and try:

// Inspect all reactive elements
State.inspectAll()

// Inspect specific element
State.inspect('#compute-demo')

// Trace HP changes
State.trace('hp', true)

Extension 5.6: Instance Management (v1.5.0)

New in v1.5.0: Dynamically instantiate and remove elements for game development - perfect for spawning enemies, creating projectiles, managing inventory items, and building dynamic UI.

Live Demo: Enemy Spawner

Code Example:

<!-- Instantiate -->
<button data-state-trigger
        data-state-instantiate="enemy-template"
        data-state-target="#game"
        data-state-insert="append"
        data-state-set-health="100"
        data-state-set-type="goblin">
  Spawn Enemy
</button>

<!-- Remove by ID -->
<button data-state-trigger
        data-state-remove="enemy-template-1">
  Remove Enemy #1
</button>

<!-- Remove all matching selector -->
<button data-state-trigger
        data-state-remove=".enemy">
  Clear All Enemies
</button>

<!-- Conditional removal -->
<button data-state-trigger
        data-state-remove=".enemy"
        data-state-condition="health <= 0">
  Remove Dead Enemies
</button>

<!-- Template element (hidden) -->
<div id="enemy-template" class="enemy" data-state>
  <!-- Your enemy content -->
</div>

Key Features:

  • Auto-Generated IDs: Cloned elements get unique IDs like enemy-1, enemy-2
  • Instance Counting: Source element gets data-{sourceId}Count attribute
  • Attribute Overrides: Use data-state-set-* to customize cloned instances
  • Insert Modes: append, prepend, before, after
  • Remove by ID: Remove single element by ID
  • Remove by Selector: Remove all matching elements (class, attribute, etc.)
  • Conditional Removal: Only remove elements that match a condition
  • Auto-Initialization: Cloned elements automatically have State.js activated
Use Cases:

Perfect for spawning enemies, creating projectiles, managing inventory items, building card collections, dynamic form fields, notification systems, and any scenario where you need to create and destroy DOM elements dynamically!


Extension 5.7: Random Number Generation (v1.5.1)

New in v1.5.1: Generate random numbers declaratively for dice rolls, loot drops, damage calculation, and procedural generation - essential for game development!

Live Demo: Dice Roller & Loot System

d6 Result
1
d20 Result
1
Loot Rarity %
0

Code Example:

<!-- Dice shorthand: 1-6 -->
<button data-state-trigger
        data-state-bind="player"
        data-state-attr="damage"
        data-state-random="6">
  Roll 1d6 Damage
</button>

<!-- Explicit range: 0-100 -->
<button data-state-trigger
        data-state-bind="loot"
        data-state-attr="rarity"
        data-state-random="0,100">
  Roll Loot Rarity
</button>

<!-- Random with chains -->
<button data-state-trigger
        data-state-bind="combat"
        data-state-attr="damage"
        data-state-random="1,6"
        data-state-trigger-chain="applyDamage">
  Attack
</button>

Key Features:

  • Dice Shorthand: data-state-random="6" generates 1-6 (common for game dice)
  • Explicit Range: data-state-random="0,100" generates 0-100 (percentages, custom ranges)
  • Zero Dependencies: Uses native Math.random()
  • Works with Conditions: Only roll if conditions are met
  • Works with Chains: Roll damage, then apply to enemy
  • Works with Intervals: Random events every N seconds
  • Works with Instantiate: Spawn enemies with random health
Use Cases:

Perfect for dice rolls, loot drops, damage calculation, spawn rates, procedural generation, probability systems, random events, and any game mechanic that needs randomization!


Extension 5.8: Expression-Based Templates (v1.6.0)

New in v1.6.0: Templating with expressions, string concatenation, and conditional logic - solves CSS content limitations!

Live Demo: Dynamic Text & Expressions

String Concatenation
75hp
Computed Expression
80hp enemy
Conditional (Ternary)
75hp
Complex Expression
Comfortable
Multiple Tokens
Lv5 - 350g
Math + String
100 max hp

Code Example:

<!-- String concatenation -->
<span data-state-text="{health}hp">75hp</span>

<!-- Computed value -->
<span data-state-text="{30 + (level - 1) * 10}hp">80hp</span>

<!-- Conditional (ternary) -->
<span data-state-text="{health > 0 ? health + 'hp' : 'DEAD'}">75hp</span>

<!-- Complex expression -->
<span data-state-text="{gold >= 500 ? 'Rich' : 'Poor'}">Poor</span>

<!-- Multiple expressions in template -->
<span data-state-text="Level {level} - {gold}g">Level 5 - 350g</span>

Key Features:

  • String Concatenation: {value + 'suffix'} combines values and text
  • Computed Expressions: {30 + (level - 1) * 10} for dynamic calculations
  • Conditional (Ternary): {condition ? 'yes' : 'no'} for if/else logic
  • Multiple Expressions: Mix static text with multiple {expressions}
  • Math Operations: +, -, *, / in expressions
  • Logical Operations: and, or, not, comparisons
  • Works with Compute: data-state-compute="label = hp + 'hp'" also supported
  • Solves CSS Limitations: calc() doesn't work in CSS content - this does!
Why Expression Templates?

CSS cannot use calc() in the content property and cannot concatenate strings from custom properties. Expression templates solve this with syntax - computed values, string concatenation, and conditional logic all in declarative HTML attributes. Perfect for game UIs, dynamic dashboards, and any scenario where you need computed display text!


Extension 5.9: Reading Element Values at Instantiate Time (v1.6.1)

New in v1.6.1: Read values from text inputs, selects, and other elements at click-time when cloning templates.

Live Demo: Task List

Code Example:

<!-- Text input -->
<input id="taskInput" type="text" placeholder="Enter task...">

<!-- Button reads input value at click-time -->
<button data-state-trigger
        data-state-instantiate="task-tpl"
        data-state-set-label-from="#taskInput">
    Add Task
</button>

<!-- Multiple inputs -->
<input id="desc" type="text">
<select id="category">
    <option value="food">Food</option>
</select>

<button data-state-trigger
        data-state-instantiate="item-tpl"
        data-state-set-desc-from="#desc"
        data-state-set-category-from="#category">
    Add Item
</button>

<!-- Template -->
<template id="task-tpl">
    <div>
        <span data-label>Default</span>
    </div>
</template>

How It Works:

  • -from Suffix: data-state-set-label-from="#input" reads from selector at click-time
  • CSS Selectors: Full querySelector support - IDs, classes, complex selectors
  • Value Resolution: Reads .value for inputs/selects, .textContent for others
  • Multiple Inputs: Use multiple -from attributes to read from different sources
  • Declarative: Enables fully declarative forms and data entry UIs
  • Override Priority: -from takes precedence over static data-state-set-*
  • Works With: <input>, <textarea>, <select>, contenteditable
Real-World Use Cases:

Perfect for task lists, budget trackers, shopping carts, form builders, note-taking apps, and any interface where users input data that needs to be cloned into templates. Eliminates the need for JavaScript event handlers to read input values - State.js handles it declaratively at trigger-time.


CSS Variables Available

State.js creates CSS variables automatically based on your configuration:

--state-visible (0 or 1)
--state-intersection (0-100%)
--state-viewport-x (0-100%)
--state-viewport-y (0-100%)
--state-[name] (raw value)
--state-[name]-percent (0-100%)
--state-[name]-normalized (0-1)
--state-[name]-deg (0-360deg)
--state-[name]-reverse (100%-0%)

The data attribute will also update in increments for CSS attribute selectors like [data-state-health="50"]{ }


Data Attributes

You can use the below data attributes for additional features

<div id="yourelement"
     data-state
     data-state-var="true"
     data-state-watch="health,mana,xp"
     data-state-toggles="active,locked"
     data-state-dimensions="true"
     data-state-media="true"
     data-state-global="true"
     data-state-increment="10">
</div>
                        

BUILD REACTIVE UI
WITH CSS/HTML

Check out the code of the documentation as an example.