https://github.com/idev-games/state-js
State.js is a CSS‑reactive framework that makes UI state and updates flow through CSS instead of JavaScript logic.
https://github.com/idev-games/state-js
Last synced: about 1 month ago
JSON representation
State.js is a CSS‑reactive framework that makes UI state and updates flow through CSS instead of JavaScript logic.
- Host: GitHub
- URL: https://github.com/idev-games/state-js
- Owner: iDev-Games
- License: mit
- Created: 2026-05-23T19:03:56.000Z (2 months ago)
- Default Branch: master
- Last Pushed: 2026-06-10T22:18:10.000Z (about 2 months ago)
- Last Synced: 2026-06-11T00:08:42.410Z (about 2 months ago)
- Language: JavaScript
- Homepage: https://idev-games.github.io/State-JS/
- Size: 5.39 MB
- Stars: 17
- Watchers: 0
- Forks: 2
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE
- Security: SECURITY.md
Awesome Lists containing this project
README
# State.js

**State.js** is a CSS‑reactive framework that makes UI state and updates flow through CSS instead of JavaScript logic, enabling data‑driven animations and reactive UIs. Build dynamic, interactive interfaces using pure CSS and HTML.
[](#license)



[](https://github.com/iDev-Games/State-JS/releases/)
---
## 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.
**A CSS-first approach to reactive interfaces.**
Using nothing but CSS, HTML and State.js, you can create:
- 📊 Dynamic dashboards and data visualizations
- 🎯 Interactive web applications with writing only CSS
- 🎨 Data-driven animations in CSS
- 🎮 Complex UIs (including game interfaces, health bars, score systems)
State.js is really lightweight and created with vanilla JavaScript without requiring any dependencies. Perfect for CSS-first development and reactive UI patterns!
---
## Installation
### Via NPM
```bash
npm i @idevgames/state-js
```
### Via CDN
```html
```
### Download Directly
Download [state.js](src/state.js) and include it in your project:
```html
```
---
## Quick Start
### 1. Basic Element Visibility Tracking
State.js automatically tracks when elements become visible:
```html
```
```css
.fadeIn {
opacity: 0;
}
.fadeIn.state {
animation: fadeIn 1s forwards ease-in-out;
}
@keyframes fadeIn {
0% { opacity: 0; }
100% { opacity: 1; }
}
```
### 2. Data Attribute Tracking (Progress Bars & Meters)
Watch data attributes and expose them as CSS variables. Here's an example using a health bar (perfect for games, but works for any progress indicator):
```html
```
```css
#player .health-bar {
width: var(--state-health-percent);
background: linear-gradient(90deg, red 0%, yellow 50%, green 100%);
}
/* Automatically triggered animations */
[data-health="0"] {
animation: death 2s forwards;
}
[data-health="10"],
[data-health="20"],
[data-health="30"] {
animation: pulse-red 1s infinite;
}
```
**Update the state** by simply changing the data attribute:
```javascript
// Change health (State.js watches and updates CSS vars automatically)
document.getElementById('player').setAttribute('data-health', '75');
```
### 3. Form Input Tracking with Auto-Binding
**No JavaScript needed!** Automatically bind form inputs to update other elements:
```html
75
```
**Bind to multiple elements** (comma-separated):
```html
```
### 4. Button Triggers (No JavaScript!)
**Make any element clickable to control state:**
```html
Player Character
Toggle Power-Up
Full Health
Add 10 Points
```
**Trigger Modes:**
- **Toggle:** `data-state-toggle="attribute"` - Flips between true/false
- **Set:** `data-state-attr="attribute"` + `data-state-value="value"` - Sets specific value
- **Increment:** `data-state-attr="attribute"` + `data-state-increment="amount"` - Adds to current value
- **Decrement:** `data-state-attr="attribute"` + `data-state-decrement="amount"` - Subtracts from current value
**Advanced: Dynamic Calculations**
Both increment and decrement support `calc()` expressions with CSS variables:
```html
Add 10
Level-scaled Click
Increasing Returns
```
**Both increment and decrement automatically respect `data-[attr]-min` and `data-[attr]-max` bounds!**
**Conditional Triggers:**
Use `data-state-condition` to only execute operations when a condition is met (perfect for costs, requirements, unlock systems):
```html
Level Up (costs 20)
Affordable Upgrade
Cast Spell
```
When a condition fails, the button gets the `state-disabled` class automatically! Style it with CSS:
```css
.state-disabled {
opacity: 0.5;
cursor: not-allowed;
pointer-events: none;
}
```
**Chaining Multiple Operations:**
Use `data-state-trigger-chain` to perform multiple operations sequentially (perfect for complex game mechanics):
```html
Level Up (costs 100 gold)
```
**Auto-firing Triggers:**
Use `data-state-autofire="true"` to automatically fire a trigger whenever its condition becomes true (perfect for passive income, auto-unlocks, achievements, and automatic progression):
```html
```
The magic: When the condition transitions from `false` → `true`, the trigger fires automatically! No click required. No visibility required. **This is the missing primitive for automatic game mechanics.**
**Works with any element:**
```html
Click me to toggle shield!
```
---
## New in v1.1.0: Seven Game Development Extensions
State.js v1.1.0 adds seven powerful declarative primitives specifically designed for game development and interactive experiences. Build complete games with **zero hand-written JavaScript logic**.
### 1. data-state-interval — Repeating Timer Triggers
Automatically fire triggers at regular intervals (perfect for passive income, cooldowns, game ticks):
```html
```
**How it works:**
- Fires the trigger automatically every N milliseconds
- Respects `data-state-condition` (won't fire if condition is false)
- Uses a single efficient shared scheduler for all interval triggers
- Perfect for idle games, passive effects, and time-based mechanics
### 2. data-state-set — Set Exact Value
Set an attribute to an exact value (unlike increment/decrement). Supports `calc()` expressions:
```html
Full Heal
Restore 50% Mana
Set Gold to Level × 100
```
**Use cases:**
- Reset/restore mechanics
- Level-scaled rewards
- Percentage-based calculations
- Achievement unlocks (set boolean flags)
### 3. data-state-text — Template String Interpolation
Display dynamic text using {token} syntax that updates automatically:
```html
```
**How it works:**
- Replaces `{attributeName}` tokens with current attribute values
- Updates automatically when any referenced attribute changes
- Supports multiple tokens in one template
- No manual display element management required
### 4. data-state-class — Conditional CSS Classes
Dynamically add/remove CSS classes based on conditions:
```html
.critical {
animation: critical-pulse 0.5s infinite;
border: 3px solid red;
}
.low-health {
filter: hue-rotate(180deg);
}
.powered-up {
box-shadow: 0 0 20px gold;
animation: glow 1s infinite;
}
.max-level {
background: linear-gradient(45deg, gold, orange);
}
```
**Features:**
- Supports up to 10 class/condition pairs per element (use `-2`, `-3`, etc.)
- Classes add/remove automatically when conditions change
- Perfect for visual state feedback
- Works with any CSS animations or effects
### 5. data-state-sound — Procedural Sound Effects
Play procedurally generated Web Audio sounds on trigger clicks (no audio files needed!):
```html
Click (+1 score)
Level Up!
Buy Item (50g)
Expensive Item (1000g)
Collect Gold
```
**Built-in sounds:**
- **click** - 80ms sawtooth beep (UI feedback)
- **levelup** - 3-note arpeggio C4→E4→G4 (achievements)
- **buy** - 100ms sine tone at 600Hz (purchases)
- **error** - 80ms square wave at 120Hz (failures)
- **coin** - Rising pitch 880→1200Hz (pickups)
**Features:**
- Zero external dependencies (uses Web Audio API)
- Procedurally generated (no audio files to load)
- Plays on trigger click before executing the action
- Respects browser autoplay policies
### 6. data-state-persist — localStorage Save/Restore
Automatically save and restore state to localStorage:
```html
```
**How it works:**
- Automatically loads saved state on page load
- Saves changes to localStorage with 500ms debounce (prevents excessive writes)
- Saves all attributes listed in `data-state-watch`
- Uses element ID as save key if `data-state-persist-key` not specified
- Perfect for idle games, progress persistence, user preferences
**Clear saved data:**
```javascript
// From browser console or your own JS:
localStorage.removeItem('my-game-save');
```
### 7. data-state-event — CustomEvent Dispatch
Dispatch CustomEvents when triggers fire (perfect for external integrations, analytics, achievements):
```html
+10 Score
document.addEventListener('state:score-increased', (e) => {
console.log('Score changed!', e.detail);
// e.detail contains:
// {
// element: <the trigger button>,
// attr: "score",
// oldValue: "0",
// newValue: "10",
// boundId: "player"
// }
});
// Track level-ups
document.addEventListener('state:level-up', (e) => {
// Send to analytics
gtag('event', 'level_up', { level: e.detail.newValue });
});
// Achievement tracking
document.addEventListener('state:achievement-unlocked', (e) => {
showNotification(`Achievement unlocked: ${e.detail.attr}!`);
});
```
**Use cases:**
- Analytics integration
- Achievement systems
- External UI updates
- Debug logging
- Third-party integrations
**Event naming:**
- Event name is prefixed with `state:` (e.g., `data-state-event="win"` → `state:win`)
- Events bubble up the DOM
- Not cancelable (fire-and-forget)
---
## Complete Game Example (All Declarative)
Combining all extensions, here's a complete idle clicker game:
```html
Mine Gold
Upgrade Pickaxe (50g)
/* Visual feedback with conditional classes */
.affordable {
background: gold;
animation: pulse 0.5s infinite;
}
#game[data-level="10"],
#game[data-level="25"],
#game[data-level="50"] {
animation: milestone-celebration 1s ease-out;
}
```
**This game has:**
- ✅ Manual clicking with dynamic rewards
- ✅ Upgrade system with costs
- ✅ Passive income ticking every second
- ✅ Auto-level-up when reaching milestones
- ✅ Sound effects for all actions
- ✅ Visual feedback for affordability
- ✅ Persistent save/load with localStorage
- ✅ Event dispatch for analytics/achievements
- ✅ **ZERO hand-written game logic JavaScript!**
---
## CSS Variables Created
State.js automatically creates CSS variables based on your configuration:
### Visibility & Position
- `--state-visible` (0 or 1)
- `--state-intersection` (0-100%)
- `--state-viewport-x` (0-100%)
- `--state-viewport-y` (0-100%)
### Watched Data Attributes
When using `data-state-watch="health,score,level"`:
- `--state-health` (raw value)
- `--state-health-percent` (0-100%)
- `--state-health-normalized` (0-1)
- `--state-health-deg` (0-360deg)
- `--state-health-reverse` (100%-0%)
- `--state-score` (raw value)
- `--state-level` (raw value)
### Form Inputs
- `--state-value` (current value)
- `--state-value-percent` (percentage of range)
- `--state-min`, `--state-max` (range bounds)
### Media Elements
- `--state-time` (current time)
- `--state-progress` (0-100%)
- `--state-playing` (0 or 1)
- `--state-volume` (0-100)
### Dimensions
- `--state-width` (px)
- `--state-height` (px)
- `--state-aspect-ratio` (calculated)
---
## Data Attributes API
### Activation
```html
```
### Configuration Attributes
| Attribute | Description | Example |
|-----------|-------------|---------|
| `data-state-var="true"` | Enable all CSS variables | `data-state-var="true"` |
| `data-state-watch="attr1,attr2"` | Watch specific data attributes | `data-state-watch="health,mana,xp"` |
| `data-state-bind="id1,id2"` | Auto-bind input to element IDs | `data-state-bind="player,enemy"` |
| `data-state-attr="attrName"` | Which attribute to update when binding | `data-state-attr="health"` |
| `data-state-value="value"` | Value to set when trigger is clicked (supports calc()) | `data-state-value="100"` or `calc(var(--state-level) * 10)` |
| `data-state-increment="amount"` | Amount to add when trigger is clicked (supports calc(), respects min/max) | `data-state-increment="10"` or `calc(var(--state-level) * 5)` |
| `data-state-decrement="amount"` | Amount to subtract when trigger is clicked (supports calc(), respects min/max) | `data-state-decrement="5"` or `calc(var(--state-cost))` |
| `data-state-trigger` | Make element clickable to trigger state changes | `data-state-trigger` |
| `data-state-trigger-chain="id1,id2"` | Click other triggers sequentially after this one | `data-state-trigger-chain="payCost,addLevel"` |
| `data-state-condition="expression"` | Only execute if condition is true (adds `state-disabled` class when false) | `data-state-condition="score >= 20"` or `"gold >= 100 and level < 10"` |
| `data-state-autofire="true"` | Automatically fire trigger when condition becomes true (requires `data-state-condition`) | `data-state-autofire="true"` |
| `data-state-toggle="attrName"` | Toggle boolean attribute on/off when clicked | `data-state-toggle="powered"` |
| `data-state-display="attrName"` | Auto-display attribute value as text | `data-state-display="health"` |
| **NEW v1.1.0** | **Game Development Extensions** | |
| `data-state-interval="ms"` | Auto-fire trigger every N milliseconds (respects conditions) | `data-state-interval="1000"` |
| `data-state-set="value"` | Set attribute to exact value (supports calc()) | `data-state-set="100"` or `calc(var(--state-max))` |
| `data-state-text="template"` | Template string with expression support (v1.6.0: supports full expressions + string concat) | `data-state-text="{30 + (level - 1) * 10}hp"` or `"{health > 0 ? health + 'hp' : 'DEAD'}"` |
| `data-state-compute="expr"` | Computed attributes with expressions (v1.6.0: supports string concatenation) | `data-state-compute="label = hp + 'hp'; max = level * 100"` |
| `data-state-class="className"` | Conditional CSS class application | `data-state-class="critical"` |
| `data-state-class-condition="expr"` | Condition for class (use with data-state-class) | `data-state-class-condition="health <= 20"` |
| `data-state-sound="soundName"` | Play Web Audio sound on trigger (click, levelup, buy, error, coin) | `data-state-sound="coin"` |
| `data-state-persist="true"` | Auto-save/restore to localStorage | `data-state-persist="true"` |
| `data-state-persist-key="key"` | localStorage key (optional, defaults to element ID) | `data-state-persist-key="my-game"` |
| `data-state-event="eventName"` | Dispatch CustomEvent as "state:eventName" | `data-state-event="score-up"` |
| **NEW v1.2.0** | **HTML Includes** | |
| `data-state-include="path"` | Fetch and inject HTML component from URL | `data-state-include="components/card.html"` |
| `data-state-toggles="attr1,attr2"` | Boolean state toggles | `data-state-toggles="active,locked"` |
| `data-state-dimensions="true"` | Track width/height | `data-state-dimensions="true"` |
| `data-state-media="true"` | Track media playback | `data-state-media="true"` |
| `data-state-global="true"` | Set CSS vars on `:root` | `data-state-global="true"` |
| `data-state-increment="10"` | Update increment for selectors | `data-state-increment="10"` |
| **NEW v1.4.0** | **Event-Based Triggers** | |
| `data-state-trigger-on="eventName"` | Fire trigger on DOM event (default: "click") | `data-state-trigger-on="mouseenter"` or `"input"` or `"focus"` |
| `data-state-debounce="ms"` | Delay trigger execution until events stop (in milliseconds) | `data-state-debounce="500"` |
| `data-state-throttle="ms"` | Limit trigger firing rate (max once per N ms) | `data-state-throttle="200"` |
| **NEW v1.5.0** | **Instance Management** | |
| `data-state-instantiate="id"` | Clone element by ID and insert into DOM | `data-state-instantiate="enemy-template"` |
| `data-state-remove="selector"` | Remove element(s) by ID or CSS selector | `data-state-remove=".enemy"` |
| `data-state-target="selector"` | Where to insert cloned element (default: body) | `data-state-target="#game"` |
| `data-state-insert="mode"` | Insert mode: append, prepend, before, after | `data-state-insert="prepend"` |
| `data-state-set-*="value"` | Override attribute on cloned element | `data-state-set-health="100"` |
| **NEW v1.5.1** | **Random Number Generation** | |
| `data-state-random="max"` | Generate random number (1 to max, dice shorthand) | `data-state-random="6"` |
| `data-state-random="min,max"` | Generate random number (min to max, explicit range) | `data-state-random="0,100"` |
### Important Notes
#### Data Attribute Naming (HTML Requirement)
**All data attributes must be lowercase.** This is an HTML specification requirement, not a State.js limitation.
```html
```
HTML automatically lowercases all attribute names. If you write `data-myAttribute`, the browser converts it to `data-myattribute`. State.js expects lowercase names throughout.
#### Trigger Chain Atomicity
**Trigger chains are not transactional.** If a link in the chain fails its condition, subsequent links still fire.
```html
```
This is intentional behavior. Each link in a chain is independent. If you need atomic transactions, use a single trigger with complex conditions instead of chains.
#### data-state-value: Numeric and Boolean Duality
**`data-state-value` works on both numeric attributes AND boolean toggles.** It writes the raw attribute directly, enabling idempotent state assignment (set to known state vs. blind toggle).
```html
Reset Health
Close Modal (idempotent - always closed)
```
Use `data-state-toggle` for flip behavior, `data-state-value` for assignment.
#### Autofire Edge Case
**Autofire won't re-trigger if condition was already true at page load.**
```html
```
Autofire detects when a condition *becomes* true (transitions from false→true). If the condition is already true at initialization, autofire won't trigger. Initialize your state values carefully to avoid this.
#### Instantiate: Beyond Game Attributes
**`data-state-set-*` works for display content, not just game attributes.** Use it to pass visual data (icons, labels, text) into templates.
```html
Unlock Achievement
```
This unlocks template use cases beyond game mechanics - UI cards, notifications, dynamic lists, etc.
---
### Per-State Configuration
```html
```
---
## New in v1.2.0: HTML Includes
**Build reusable, modular HTML components** - just like any modern framework, but with zero build tools.
HTML Includes let you fetch and inject components declaratively. Create a component once, use it everywhere. Perfect for health bars, UI cards, player stats, inventory items, or any repeating UI pattern.
### Basic Usage
```html
```
### Creating a Component
**Option 1: External File (for modularity)**
**components/health-bar.html:**
```html
```
**Option 2: Inline Template (for performance)**
```html
```
### How It Works
**Template Mode (`#id`):**
1. **Clones** from `` tag or element by ID (instant, zero latency)
2. **Merges** attributes from include element to cloned component
3. **Replaces** include element with component
4. **Initializes** State.js on the injected component
**Fetch Mode (`path.html`):**
1. **Fetches** HTML from URL (cached after first load)
2. **Merges** attributes from include element to fetched component
3. **Replaces** include element with component
4. **Initializes** State.js on the injected component
All State.js features (triggers, persistence, intervals, sounds, etc.) work perfectly in included components!
### Use Cases
- **Health/Mana Bars** - Define once, use for player, enemies, NPCs
- **Inventory Items** - Consistent item cards across inventory, shop, tooltip
- **UI Cards** - Stat displays, achievement cards, notifications
- **Player Stats** - Level, XP, gold displays
- **Navigation** - Shared headers, footers, menus across pages
### Configuration
| Attribute | Description | Example |
|-----------|-------------|---------|
| `data-state-include="path.html"` | Fetch and inject HTML from URL | `data-state-include="components/card.html"` |
| `data-state-include="#id"` | Clone from template or element by ID | `data-state-include="#card-template"` |
**Note:** Any other attributes on the include element are copied to the injected component, allowing you to override default values.
### Performance Strategy
**Use templates (`#id`) for:**
- Critical, frequently-used components (zero latency)
- Components needed immediately on page load
- Single-page apps where all components fit in initial HTML
**Use files (`path.html`) for:**
- Large component libraries (keeps HTML small)
- Components used across multiple pages (modularity)
- Production apps with proper HTTP caching
**Local Development:** File-based includes require HTTP/HTTPS (browser security prevents `file://` fetching). Run any simple local server - Python's `python -m http.server`, Node's `npx http-server`, or VS Code Live Server. Template-based includes work anywhere, including `file://`!
### Security Considerations
⚠️ **Important**: For security, external file fetches are **disabled by default** in State.js v1.4.2+.
**Template-based includes are always safe** and require no configuration:
```html
```
**To enable external file fetches**:
```javascript
// Only enable if you trust the source AND use HTTPS
state.allowExternalIncludes = true;
```
**Security Best Practices**:
1. **Prefer templates over external files** when possible
2. **Use HTTPS only** - never fetch over HTTP
3. **Same-origin policy** - fetch from your own domain
4. **Content Security Policy** - add CSP headers to your server
5. **DOMPurify (optional)** - for extra protection with external content:
```html
// DOMPurify auto-detected and used if available
state.allowExternalIncludes = true;
```
**Attack Vectors to Avoid**:
- ❌ User-controlled URLs: `data-state-include="${userInput}"`
- ❌ HTTP endpoints: `data-state-include="http://..."`
- ❌ Untrusted CDNs: `data-state-include="https://random-cdn.com/..."`
- ❌ Third-party domains without CORS/CSP protection
**Why Template-Based Includes Are Secure**:
Templates (`#id`) are already in your HTML - if they're malicious, your page is already compromised. The security boundary is your deployment pipeline, not runtime injection.
---
## New in v1.3.0: Computed State & Debug API
### Computed State
**Automatically calculate derived values** from your data attributes - no manual updates needed!
Computed state keeps calculated values in sync with their dependencies. Perfect for health percentages, damage calculations, level-up requirements, or any derived game logic.
#### Basic Usage
```html
HP: %
```
#### Multiple Computations
Use semicolons to define multiple computed values:
```html
```
#### Supported Expressions
- **Math operators**: `+`, `-`, `*`, `/`, `%`, `()`
- **Comparisons**: `<`, `>`, `<=`, `>=`, `==`, `!=`
- **Logical operators**: `&&`, `||`, `!`
- **Ternary**: `condition ? valueA : valueB`
- **Attribute references**: Use attribute names directly (e.g., `hp`, `maxHp`)
#### Examples
```html
```
#### How It Works
1. **Parse** - State.js parses your compute expressions on setup
2. **Auto-update** - When any dependency changes, computed values recalculate automatically
3. **Expose** - Computed values become `data-${name}` attributes and `--state-${name}` CSS variables
4. **Display** - Use `data-state-display` to show computed values in your UI
#### Use Cases
- **Health/Mana percentages** for progress bars
- **Damage calculations** for combat systems
- **Level-up requirements** (XP needed, stats gained)
- **Resource management** (inventory space, currency conversions)
- **Status checks** (isCritical, canAfford, isComplete)
- **Score calculations** (combos, multipliers, totals)
---
### Debug API
**Console tools for inspecting and debugging reactive state** - perfect for development and testing.
State.js provides a JavaScript API accessible via the browser console for debugging your application state.
#### State.inspectAll()
Returns an array of all reactive elements with their current state:
```javascript
// In browser console
State.inspectAll()
/* Returns:
[
{
element:
,
id: "player",
state: { hp: "75", maxHp: "100", hpPercent: "75" },
config: { ... }
},
...
]
*/
```
#### State.inspect(selector)
Inspect a specific element's state:
```javascript
State.inspect('#player')
/* Returns:
{
element:
,
id: "player",
state: { hp: "75", maxHp: "100", hpPercent: "75" },
config: { watchAttrs: ["hp", "maxHp"], ... }
}
*/
```
#### State.trace(attrName, enabled)
Enable/disable console logging for attribute changes:
```javascript
// Enable tracing for HP changes
State.trace('hp', true)
// Now every time data-hp changes, you'll see:
// State.js [hp]: { element:
, id: "player", attribute: "hp", oldValue: "75", newValue: "65" }
// Disable tracing
State.trace('hp', false)
```
#### Usage Examples
```javascript
// Debug all reactive elements
const elements = State.inspectAll()
console.table(elements.map(e => e.state))
// Check specific element state
const player = State.inspect('#player')
console.log('Player HP:', player.state.hp)
// Trace multiple attributes
State.trace('hp')
State.trace('gold')
State.trace('xp')
// Now see all changes to hp, gold, and xp in real-time
// Find elements with low HP
State.inspectAll()
.filter(e => parseFloat(e.state.hp) < 20)
.forEach(e => console.log(`${e.id} is critical!`))
```
#### Use Cases
- **Debugging** - See all state changes in real-time
- **Testing** - Verify attribute values during development
- **Optimization** - Track which attributes update frequently
- **Learning** - Understand how State.js works internally
---
## New in v1.4.0: Event-Based Triggers
**Fire triggers on ANY DOM event** - not just clicks! Listen to hover, focus, input, scroll, and more with built-in debounce/throttle support.
Event-based triggers let you respond to any DOM event declaratively. Perfect for form interactions, hover effects, scroll tracking, visibility detection, and real-time input validation - all without writing JavaScript event listeners.
### Basic Usage
Use `data-state-trigger-on` to specify which event should fire the trigger:
```html
Click to score
Click to score
Hover over me!
Submit
```
### Supported Events
**Mouse Events:**
- `click` - Default trigger behavior
- `dblclick` - Double-click
- `mouseenter` - Mouse enters element
- `mouseleave` - Mouse leaves element
- `mouseover` - Mouse moves over element
- `mouseout` - Mouse moves out of element
**Form Events:**
- `input` - Text input, range slider changes (fires on every keystroke)
- `change` - Select dropdowns, checkboxes, radio buttons (fires on blur/commit)
- `focus` - Element gains focus
- `blur` - Element loses focus
- `submit` - Form submission (automatically calls `preventDefault()`)
**Keyboard Events:**
- `keydown` - Key is pressed down
- `keyup` - Key is released
- `keypress` - Key is pressed (deprecated but supported)
**Scroll Events:**
- `scroll` - Element scrolls (use with throttle!)
**Custom Events:**
- `intersect` - Element becomes visible (custom event from IntersectionObserver)
- Any CustomEvent dispatched via JavaScript
### Debounce (Delay After Rapid Events)
Use `data-state-debounce` to delay execution until events stop firing:
```html
```
**How debounce works:**
1. Event fires (e.g., user types a character)
2. Timer starts counting down from specified ms
3. If another event fires before timer completes, reset the timer
4. When timer completes without interruption, execute trigger
5. **Use for:** Text input, resize, autocomplete, validation
### Throttle (Limit Firing Rate)
Use `data-state-throttle` to limit how often a trigger can fire:
```html
Scroll content...
Track mouse movement
```
**How throttle works:**
1. Event fires and trigger executes immediately
2. Start cooldown timer for specified ms
3. Any events during cooldown are ignored
4. After cooldown completes, next event can fire
5. **Use for:** Scroll, mousemove, resize, frequent events
**Debounce vs Throttle:**
- **Debounce:** Wait until activity stops → fires once at the end
- **Throttle:** Fire regularly during activity → fires multiple times at limited rate
### Visibility Detection
The `intersect` event fires when an element enters the viewport:
```html
Content that tracks visibility
```
**How it works:**
- State.js uses IntersectionObserver to track visibility
- When element becomes visible for the first time, dispatches `intersect` CustomEvent
- Trigger fires and can update state, trigger chains, play sounds, etc.
- **Use for:** Analytics, lazy loading, scroll-triggered animations, achievement tracking
### Form Auto-Submit Prevention
Form `submit` events automatically call `preventDefault()` to prevent page reload:
```html
Submit
Submissions: 0
```
**No JavaScript required!** The form won't reload the page - State.js handles it automatically.
### Combining with Conditions
Event triggers respect `data-state-condition` just like click triggers:
```html
Hover to collect gold (only when active)
```
**Result:** Triggers are disabled (get `state-disabled` class) when condition is false, just like click triggers!
### Real-World Examples
#### Live Search Counter
```html
Searches performed: 0
```
#### Scroll Progress Tracker
```html
Long scrollable content...
Scroll events: 0
```
#### Form Field Tracking
```html
Option 1
Option 2
Fields focused: 0
Changes made: 0
```
### Use Cases
**Event-based triggers are perfect for:**
- 📝 Live search/autocomplete (input + debounce)
- 📊 Analytics tracking (focus, scroll, visibility)
- 🎯 Hover effects and interactions (mouseenter/leave)
- 📋 Form validation and submission (submit, change, blur)
- 📜 Scroll progress indicators (scroll + throttle)
- 👀 Lazy loading and content reveal (intersect)
- ⌨️ Keyboard shortcut tracking (keydown/up)
- 🎮 Interactive games (mousemove, keypress)
- 📱 Mobile gesture tracking (with Touch.js integration)
### Performance Tips
1. **Always throttle scroll and mousemove events** (100-200ms recommended)
2. **Debounce text input** for search/autocomplete (300-500ms recommended)
3. **Use intersect for lazy loading** instead of scroll events
4. **Combine with conditions** to disable triggers when not needed
5. **Prefer change over input** for dropdowns/checkboxes (fires less frequently)
### Configuration
| Attribute | Description | Example |
|-----------|-------------|---------|
| `data-state-trigger-on="eventName"` | Which DOM event fires the trigger (default: "click") | `data-state-trigger-on="mouseenter"` |
| `data-state-debounce="ms"` | Delay trigger execution until events stop (in milliseconds) | `data-state-debounce="500"` |
| `data-state-throttle="ms"` | Limit trigger firing rate (max once per N milliseconds) | `data-state-throttle="200"` |
**Special behaviors:**
- `submit` events automatically call `event.preventDefault()`
- `intersect` is a custom event fired by IntersectionObserver
- `click` is the default if `data-state-trigger-on` is omitted
- Triggers with `trigger-on="click"` still get `cursor: pointer` style
---
## New in v1.5.0: Instance Management
Dynamically create and remove DOM elements using declarative triggers - perfect for spawning enemies, creating projectiles, managing inventory items, and building dynamic UI systems.
### Basic Instantiation
Clone any element by ID and insert it into the DOM:
```html
Spawn Enemy
```
**What happens:**
1. Clones `#enemy-template` element
2. Generates unique ID: `enemy-template-1`, `enemy-template-2`, etc.
3. Inserts into `#game` container
4. Automatically initializes State.js on the clone
5. Updates instance count on source element
### Attribute Overrides
Customize each cloned instance with different attributes:
```html
Spawn Goblin (Lvl 5, 100 HP)
Spawn Orc (Lvl 10, 200 HP)
```
**How attribute overrides work:**
- Use `data-state-set-*` to set attributes on cloned instances
- Most attributes become `data-*` (e.g., `data-state-set-health="100"` → `data-health="100"`)
- **Special case:** `data-state-set-class="enemy"` sets the actual `class` attribute (not `data-class`)
**Common use cases:**
```html
```
### Insert Modes
Control where the cloned element is inserted:
```html
Add Item (End)
Add Item (Start)
Add Before
Add After
```
### Instance Counting
Source elements automatically track how many instances have been created:
```html
Enemies spawned: 0
```
**Automatic counter attribute:** `data-{sourceId}Count` is updated on the source element each time an instance is created.
### Removing Elements
Remove elements by ID or CSS selector:
```html
Remove Enemy #1
Clear All Enemies
Remove All Goblins
```
**How it works:**
- **ID removal** (no `.` or `[`): Removes single element by ID
- **Selector removal** (starts with `.` or contains `[`): Removes all matching elements
### Conditional Removal
Only remove elements that meet specific conditions:
```html
Remove Dead Enemies
Sell Junk Items
```
### Complete Example: Enemy Spawner
```html
Spawn Goblin
Spawn Orc
Remove Dead
Clear All
Total spawned: 0
Enemy
HP: 100
Attack (-25 HP)
```
### Use Cases
**Perfect for:**
- 🎮 **Game Development:** Spawn enemies, projectiles, particles, loot drops
- 📦 **Inventory Systems:** Add/remove items, manage equipment slots
- 🃏 **Card Games:** Deal cards, shuffle decks, create hands
- 📝 **Dynamic Forms:** Add/remove form fields, repeating sections
- 🔔 **Notifications:** Create toast messages, alerts, popups
- 📊 **Data Visualization:** Generate chart elements, data points
- 🛒 **Shopping Carts:** Add/remove products, update quantities
- 💬 **Chat Systems:** Add messages, manage conversation threads
### Security
Instance management uses `DOMParser` for secure element cloning (same as v1.4.2 HTML includes):
- ✅ No `innerHTML` usage
- ✅ Safe from XSS attacks
- ✅ CSP compliant
- ✅ Zero external dependencies
### Reading Element Values at Instantiate Time (v1.6.1)
**The final piece for pur declarative apps** - Read values from form inputs or any element at trigger-time.
Use `data-state-set-*-from` to capture user input when cloning, enabling fully declarative forms without any JavaScript.
#### Basic Usage
```html
Add Task
New task
```
**How it works:**
1. User types in `#taskName` input
2. Click triggers instantiate
3. State.js reads `input.value` at click-time
4. Sets `data-label` on the clone with the input's current value
5. Template displays the user's text - no JavaScript required!
#### Supported Elements
**The `-from` attribute works with any element:**
- **``** - Reads `.value` property
- **``** - Reads `.value` property
- **``** - Reads `.value` property (selected option)
- **Contenteditable** - Reads `.textContent` property
- **Any element** - Falls back to `.textContent`
#### Multiple Inputs Example
```html
Food
Transport
Add Expense
```
#### Complex Selectors
**Full CSS selector support** - not limited to IDs:
```html
```
#### Combining Static and Dynamic Values
You can combine static fallbacks with dynamic `-from` values:
```html
```
**Priority:** `-from` takes precedence if the element is found and has a value.
#### Real-World Example: Budget Tracker
**Before v1.6.1 (required JavaScript):**
```html
Add
// JS needed to read input value
addBtn.addEventListener('mousedown', () => {
addBtn.setAttribute('data-state-set-label',
document.getElementById('incomeName').value);
});
```
**After v1.6.1 (fully declerative):**
```html
Add
```
**Result:** The 15 lines of JavaScript are eliminated entirely. Pure declarative forms.
#### Contenteditable Support
Works with rich text editors:
```html
Type your formatted note here
Save Note
```
#### Edge Cases
**Element not found:**
- Treated as empty string
- Instantiate continues normally
- Clone gets empty value for that attribute
**Empty input value:**
- Sets empty string on clone: `data-label=""`
- Does not fall back to static value
- `-from` always wins when element exists
**Should State.js clear the input after adding?**
- No - that's a UX decision that varies by use case
- Some apps clear (budget demo style)
- Some apps keep value (edit mode style)
- Use trigger chains to reset if needed: `data-state-trigger-chain="addEntry,clearInput"`
### Configuration
| Attribute | Description | Example |
|-----------|-------------|---------|
| `data-state-instantiate="id"` | ID of element to clone | `data-state-instantiate="enemy"` |
| `data-state-remove="selector"` | ID or CSS selector of element(s) to remove | `data-state-remove=".enemy"` |
| `data-state-target="selector"` | Where to insert cloned element (default: body) | `data-state-target="#game"` |
| `data-state-insert="mode"` | Insert mode: append, prepend, before, after (default: append) | `data-state-insert="prepend"` |
| `data-state-set-*="value"` | Override attribute on cloned element (becomes `data-*`) | `data-state-set-health="100"` → `data-health="100"` |
| `data-state-set-class="value"` | Special: Sets actual `class` attribute (not `data-class`) | `data-state-set-class="enemy"` → `class="enemy"` |
| `data-state-condition="expr"` | Condition for removal (only with remove) | `data-state-condition="health <= 0"` |
**Auto-generated:**
- Unique IDs: `{sourceId}-{counter}` (e.g., `enemy-1`, `enemy-2`)
- Instance count: `data-{sourceId}Count` on source element
- Clones automatically have `display: none` removed if present on template
---
## New in v1.5.1: Random Number Generation
**Essential for game development** - Generate random numbers declaratively using pure HTML attributes.
State.js provides simple, zero-dependency random number generation for:
- 🎲 Dice rolls
- 🎁 Loot drops
- ⚔️ Damage calculation
- 🎰 Probability systems
- 🎮 Procedural generation
### Basic Usage
```html
Roll 1d6 Damage
Roll Loot Rarity
Spawn with Random HP
```
### Syntax Options
**Dice shorthand (1 to N):**
```html
data-state-random="6"
data-state-random="20"
data-state-random="100"
```
**Explicit range (min to max):**
```html
data-state-random="1,6"
data-state-random="0,100"
data-state-random="10,50"
```
### Advanced Patterns
**Random with conditions:**
```html
Try Your Luck
```
**Random with trigger chains:**
```html
Attack
```
**Random intervals:**
```html
```
**Random with instantiate:**
```html
Spawn Random Enemy
```
### Configuration
| Attribute | Description | Example |
|-----------|-------------|---------|
| `data-state-random="max"` | Dice shorthand: 1 to max | `data-state-random="6"` → 1-6 |
| `data-state-random="min,max"` | Explicit range: min to max | `data-state-random="0,100"` → 0-100 |
**Requirements:**
- Must have `data-state-trigger` attribute
- Must specify `data-state-attr` (which attribute to set)
- Must have `data-state-bind` or be inside `[data-state]` element
- Uses native `Math.random()` - zero dependencies
**How it works:**
1. When trigger fires, checks for `data-state-random`
2. Parses range (single number = dice shorthand, two numbers = explicit)
3. Generates random integer in range using `Math.floor(Math.random() * (max - min + 1)) + min`
4. Sets the attribute value to the random number
5. Works seamlessly with conditions, chains, intervals, and all other State.js features
---
## New in v1.6.0: Expression-Based Templates
**Templating without leaving HTML** - Computed values, string concatenation, and conditional logic in pure declarative attributes.
CSS has fundamental limitations - you cannot use `calc()` in the `content` property, and you cannot concatenate strings from custom properties. State.js v1.6.0 solves this with expression-based templates.
### The Problem (Before v1.6.0)
```css
/* This doesn't work in CSS! */
.enemy-label:after {
--hp: calc(30 + (var(--state-level) - 1) * 10);
content: var(--hp) "hp"; /* Can't concatenate! */
}
```
### The Solution (v1.6.0)
```html
30hp
100hp
Level 5 - 750/1000 XP
```
### Basic Usage
**Simple attribute display (still works):**
```html
HP: 100
```
**String concatenation:**
```html
100hp
500 gold
```
**Computed numeric expressions:**
```html
30hp
10-15
75%
```
**Conditional display with ternary:**
```html
100hp
Common
Online
```
**Multiple expressions in one template:**
```html
Level 5 Hero
Warrior - 75/100hp
XP: 750/1000 (75%)
```
### Advanced Patterns
**Nested expressions:**
```html
Novice
```
**Math operations:**
```html
{health + shield}hp total
DPS: 45
```
**Logical operations:**
```html
Locked
```
### String Concatenation in Computed State
**v1.6.0 also adds string concatenation to `data-state-compute`:**
```html
30hp
Alive
```
**Compute creates attributes, then display them:**
- Computation result stored as `data-hpLabel="30hp"`
- Can be used in CSS, displayed with `data-state-display`, or referenced elsewhere
- Reusable across multiple elements
### Expression Syntax Reference
**Operators supported:**
- **Arithmetic**: `+`, `-`, `*`, `/`
- **Comparison**: `==`, `!=`, `<`, `>`, `<=`, `>=`
- **Logical**: `and`/`&&`, `or`/`||`, `not`/`!`
- **Ternary**: `condition ? trueValue : falseValue`
- **Grouping**: `(expression)`
**String concatenation:**
```html
{value + 'suffix'}
{'prefix' + value}
{value1 + ' ' + value2}
```
**Values:**
- Attribute names: `{health}` resolves to `data-health` attribute value
- Numbers: `{100}`, `{3.14}`
- Strings: `{'text'}`, `{"text"}`
- Booleans: `{true}`, `{false}`
**Examples:**
```html
{10 + 5 + 'hp'} → "15hp"
{health > 0 ? health + 'hp' : 'DEAD'} → "100hp" or "DEAD"
{level > 10 ? 'Veteran Lv' + level : 'Novice'} → "Veteran Lv15"
{(damage + bonusDmg) * critMultiplier + ' damage'} → "150 damage"
```
### Why Expression Templates?
**Solves CSS limitations:**
- ✅ Computed values in display text (impossible in CSS `content`)
- ✅ String concatenation (impossible in CSS)
- ✅ Conditional text (no if/else in CSS)
- ✅ Complex calculations for display
**More intuitive than alternatives:**
- Better than: Compute → attribute → display (two steps)
- Syntax: `{expr}` feels natural
- Inline conditionals: Ternary in template, not separate elements
### Use Cases
**Game development:**
```html
30hp
Common
...
```
**Dynamic UIs:**
```html
Active now
3 items (150 gold)
7/10 tasks (70%)
```
**Form validation:**
```html
Valid
```
### Configuration
**Expression Templates:**
| Attribute | Description | Example |
|-----------|-------------|---------|
| `data-state-text="{expr}"` | Template with expressions | `data-state-text="{hp}hp"` |
| `data-state-text="text {expr} text"` | Mixed static + dynamic | `data-state-text="Level {level} Hero"` |
**Computed State with Strings:**
| Attribute | Description | Example |
|-----------|-------------|---------|
| `data-state-compute="name=expr"` | Compute and store result | `data-state-compute="label = hp + 'hp'"` |
| `data-state-compute="a=expr; b=expr"` | Multiple computations | `data-state-compute="sum = a + b; label = sum + 'hp'"` |
**Requirements:**
- `data-state-text` requires `data-state-bind` pointing to element with watched attributes
- Expressions evaluated using existing State.js expression parser
- String concatenation using `+` operator (auto-detects strings vs numbers)
- All watched attributes available as identifiers in expressions
---
## State-Animations.css
State.js includes **state-animations.css** - a companion stylesheet with predefined animations for common UI patterns and interactive elements.
### Include in your project:
```html
```
### Available Animation Classes:
#### UI Feedback & Notifications
- `.state-notification` - Notification slide
- `.state-warning` - Warning shake
- `.state-success` - Success bounce
- `.state-error` - Error shake
- `.state-loading` - Loading spin
#### Progress & Meter States
- `.state-health-low` - Low value warning pulse
- `.state-health-critical` - Critical state shake
- `[data-health="0"]` - Empty state animation
- `[data-health="100"]` - Full/complete glow
#### Counter & Score Animations
- `.state-score-increase` - Value increase pop
- `.state-score-milestone` - Milestone celebration
- `.state-level-up` - Level/tier change flash
#### Status Indicators
- `.state-powered` - Active/powered state glow
- `.state-invincible` - Protected state shimmer
- `.state-shielded` - Shield/protection pulse
- `.state-stunned` - Disabled/paused effect
- `.state-poisoned` - Negative effect pulse
- `.state-frozen` - Frozen/locked shake
- `.state-burning` - Active damage flicker
- `.state-healing` - Positive effect sparkle
[View full animation documentation →](animations.html)
---
## Advanced Examples
### Multi-Attribute UI Component (Character Stats Demo)
```html
Level
```
### Video Progress Indicator
```html
video::after {
content: "";
width: var(--state-progress);
height: 5px;
background: red;
position: absolute;
bottom: 0;
left: 0;
}
```
### Boolean Toggle States
```html
```
```css
/* Automatically applied classes */
.state-active {
filter: brightness(1.2);
transform: scale(1.05);
}
.state-locked {
filter: grayscale(1) brightness(0.6);
cursor: not-allowed;
}
.state-complete {
animation: complete-check 0.5s forwards;
}
```
### Clicker Game (Fully declarative)
```html
Score: 0
Click Me!
/* Celebrate milestones with CSS alone */
#clicker[data-score="10"],
#clicker[data-score="20"],
#clicker[data-score="30"] {
animation: milestone-burst 0.5s ease-out;
}
#clicker[data-score="100"] {
animation: victory-flash 1s ease-out;
}
/* Progress bar using CSS variables */
#clicker::after {
content: "";
width: var(--state-score-percent);
height: 10px;
background: linear-gradient(90deg, red, yellow, green);
}
```
### Volume Control (Increment & Decrement with Auto-Clamping)
```html
Volume: 50%
-
+
```
### Idle Game with Dynamic Scaling (No JavaScript!)
```html
Gold: 0
Level: 1
Click Power: 1
Mine Gold
Upgrade Pick (+1 power)
Level Up
/* Different animations per level */
#idleGame[data-level="5"],
#idleGame[data-level="10"] {
animation: level-milestone 1s ease-out;
}
/* Click power visualization */
#idleGame::after {
content: "";
width: calc(var(--state-clickPower) * 10px);
height: 5px;
background: gold;
}
```
---
## Integration with Other Libraries
State.js is part of a complete CSS/HTML UI development toolkit from iDev Games:
### The iDev Games CSS Framework Suite
**Five libraries working together for pure CSS/HTML interactive experiences:**
1. **[Keys.js](https://github.com/iDev-Games/Keys-JS)** - Keyboard input tracking
- `--key-space`, `--key-up`, `--key-down`, etc.
2. **[Cursor.js](https://github.com/iDev-Games/Cursor-JS)** - Mouse position tracking
- `--cursor-x`, `--cursor-y`, `--cursor-speed`, etc.
3. **[Touch.js](https://github.com/iDev-Games/Touch-JS)** - Touch gesture tracking
- `--touch-x`, `--touch-velocity-x`, `--touch-distance`, etc.
4. **[Motion.js](https://github.com/iDev-Games/Motion-JS)** - Time/animation tracking
- `--motion-progress`, `--motion-time`, `--motion-loop`, etc.
5. **State.js** ⭐ - UI state & data binding
- `--state-health`, `--state-score`, `--state-level`, etc.
### Combined Example
```html
Score:
/* When health is low AND cursor is idle */
body.cursor-idle [data-health="10"],
body.cursor-idle [data-health="20"] {
animation: warning-pulse 1s infinite;
}
/* When up arrow pressed AND health full */
.key-up[data-health="100"] {
animation: victory-jump 0.5s ease-out;
}
```
**Result:** A complete interactive UI system with dynamic data, user input tracking, and reactive animations - all in CSS! Perfect for games, dashboards, data visualizations, and interactive experiences.
---
## Browser Support
State.js uses modern browser APIs:
- IntersectionObserver API
- MutationObserver API
- CSS Custom Properties
**Supported browsers:**
- Chrome/Edge 58+
- Firefox 55+
- Safari 12.1+
- Opera 45+
---
## Performance
State.js is optimized for performance:
- ✅ Passive event listeners
- ✅ requestAnimationFrame for DOM updates
- ✅ Map-based attribute caching
- ✅ Conditional updates (only when values change)
- ✅ Efficient MutationObserver usage
---
## Documentation
- [Live Demo & Documentation](index.html)
- [state-Animations.css Documentation](animations.html)
- [GitHub Repository](https://github.com/iDev-Games/State-JS)
---
## Examples
Check out the documentation page code as an example:
[https://github.com/iDev-Games/State-JS/blob/master/index.html](https://github.com/iDev-Games/State-JS/blob/master/index.html)
---
## Philosophy
**Declarative over Imperative**
State.js follows the same philosophy as all iDev Games libraries:
- ✅ Describe what you want (HTML data attributes)
- ✅ Style how it looks (CSS)
- ❌ No complex JavaScript APIs to learn
- ❌ No framework dependencies
**The goal:** Enable developers to build reactive, data-driven interfaces using HTML and CSS skills they already have - whether for dashboards, web apps, visualizations, or games.
---
## License
MIT License - see [LICENSE](LICENSE) file for details
---
## Author
**iDev Games**
- GitHub: [@iDev-Games](https://github.com/iDev-Games)
- Dev.to: [@idevgames](https://dev.to/idevgames)
---
## Contributing
Contributions, issues, and feature requests are welcome!
Feel free to check the [issues page](https://github.com/iDev-Games/State-JS/issues).
---
## Show your support
Give a ⭐️ if this project helped you!