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>
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; } }
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;
}
Move the slider to change the health value (no JavaScript needed!):
<!-- 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>
Make buttons control state without writing JavaScript! Use increment for clickers and counters:
<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>
Automatically fire triggers when conditions become true - perfect for passive income, auto-unlocks, achievements, and automatic progression!
<!-- 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 false → true, the trigger fires automatically! No click required. No visibility required. This is the missing primitive for automatic game mechanics.
Seven new declarative primitives for building complete games with ZERO hand-written JavaScript logic!
Triggers that fire automatically on a timer - perfect for passive income and cooldowns!
Each worker generates 1 gold per second automatically! Hire more workers to increase your income.
<!-- 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>
data-state-set sets exact values, data-state-text uses template strings!
Text updates automatically using {token} syntax! Full Heal uses data-state-set with calc() to restore to max.
<!-- 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>
Automatically add/remove CSS classes based on conditions - perfect for visual feedback!
≤50 HP: Yellow pulse | ≤20 HP: Red shake | 100 HP: Green glow
<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>
Fire-and-forget Web Audio sounds - no audio files needed! Click the buttons to hear:
click • coin • buy • levelup
Generated with Web Audio API - zero external dependencies!
<button data-state-trigger
data-state-bind="player"
data-state-attr="score"
data-state-increment="10"
data-state-sound="coin">
Collect Coin
</button>
Triggers can now fire on ANY DOM event - not just clicks! Includes debounce/throttle for performance.
Fires on mouseenter event
Throttled to 200ms - fires at most 5x per second
Keep scrolling...
Almost there...
click • mouseenter • mouseleave • focus • blur • input • change • submit • keydown • keyup • scroll • intersect (visibility) • and more!
<!-- 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>
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)submit events automatically call preventDefault()intersect event fires when element enters viewportdata-state-persist saves to localStorage, data-state-event dispatches CustomEvents!
1. Click "Increment & Save" a few times
2. Reload the page - your count persists!
3. Watch the event log below for CustomEvents
<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>
data-state-include enables modular, reusable HTML components - just like other frameworks, but with zero build tools!
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.
<!-- 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>
<!-- 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>
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!
data-state-compute automatically calculates derived values from your data - perfect for percentages, damage formulas, and game logic!
<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>
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!
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)
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.
<!-- 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>
enemy-1, enemy-2data-{sourceId}Count attributedata-state-set-* to customize cloned instancesPerfect 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!
New in v1.5.1: Generate random numbers declaratively for dice rolls, loot drops, damage calculation, and procedural generation - essential for game development!
<!-- 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>
data-state-random="6" generates 1-6 (common for game dice)data-state-random="0,100" generates 0-100 (percentages, custom ranges)Math.random()Perfect for dice rolls, loot drops, damage calculation, spawn rates, procedural generation, probability systems, random events, and any game mechanic that needs randomization!
New in v1.6.0: Templating with expressions, string concatenation, and conditional logic - solves CSS content limitations!
<!-- 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>
{value + 'suffix'} combines values and text{30 + (level - 1) * 10} for dynamic calculations{condition ? 'yes' : 'no'} for if/else logic{expressions}+, -, *, / in expressionsand, or, not, comparisonsdata-state-compute="label = hp + 'hp'" also supportedcalc() doesn't work in CSS content - this does!
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!
New in v1.6.1: Read values from text inputs, selects, and other elements at click-time when cloning templates.
<!-- 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>
data-state-set-label-from="#input" reads from selector at click-timequerySelector support - IDs, classes, complex selectors.value for inputs/selects, .textContent for others-from attributes to read from different sources-from takes precedence over static data-state-set-*<input>, <textarea>, <select>, contenteditablePerfect 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.
State.js creates CSS variables automatically based on your configuration:
The data attribute will also update in increments for CSS attribute selectors like [data-state-health="50"]{ }
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>