An open API service indexing awesome lists of open source software.

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.

Awesome Lists containing this project

README

          

# State.js

![State.js Logo](logo.png)

**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://img.shields.io/badge/License-MIT-blue)](#license)
![npm bundle size](https://img.shields.io/bundlephobia/min/%40idevgames%2Fstate-js)
![npm](https://img.shields.io/npm/dy/%40idevgames%2Fstate-js?logo=NPM)
![jsDelivr hits (npm)](https://img.shields.io/jsdelivr/npm/hm/%40idevgames%2Fstate-js)
[![GitHub tag](https://img.shields.io/github/tag/iDev-Games/State-JS?include_prereleases=&sort=semver&color=blue)](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!