···11+# Volt Bindings
22+33+Bindings connect reactive state to the DOM using `data-volt-*` attributes. Each binding evaluates expressions and updates the DOM when dependencies change.
44+55+All bindings support the full expression syntax documented in [Expression Evaluation](./expressions), including property access, operators, function calls, and signal unwrapping.
66+77+## Content
88+99+### Text Content
1010+1111+The `data-volt-text` binding updates an element's text content:
1212+1313+```html
1414+<p data-volt-text="message">Fallback text</p>
1515+```
1616+1717+Text content is automatically escaped for security. Use this binding for any user-generated content to prevent XSS attacks.
1818+1919+The fallback text (between the opening and closing tags) is displayed until the framework mounts and evaluates the expression.
2020+2121+### HTML Content
2222+2323+The `data-volt-html` binding updates an element's innerHTML:
2424+2525+```html
2626+<div data-volt-html="richContent"></div>
2727+```
2828+2929+This binding renders raw HTML without escaping. Only use it with trusted content. Never use `data-volt-html` with user-generated content unless it has been sanitized.
3030+3131+The binding removes existing children before inserting new content. Event listeners on removed elements are not automatically cleaned up—prefer using `data-volt-if` for conditional content with event handlers.
3232+3333+## Attributes
3434+3535+### Generic Attributes
3636+3737+The `data-volt-bind:*` syntax binds any HTML attribute:
3838+3939+```html
4040+<img data-volt-bind:src="imageUrl" data-volt-bind:alt="imageAlt">
4141+<a data-volt-bind:href="linkUrl" data-volt-bind:target="linkTarget">Link</a>
4242+<input data-volt-bind:disabled="isDisabled" data-volt-bind:placeholder="placeholderText">
4343+```
4444+4545+The attribute name follows the colon. The expression value is converted to a string and set as the attribute value.
4646+4747+For boolean attributes (`disabled`, `checked`, `required`, etc.), the attribute is added when the expression is truthy and removed when falsy.
4848+4949+### Class Binding
5050+5151+The `data-volt-class` binding toggles CSS classes based on an object expression:
5252+5353+```html
5454+<div data-volt-class="{ active: isActive, disabled: !canInteract, 'has-error': hasError }">
5555+```
5656+5757+Each key in the object is a class name. When the corresponding value is truthy, the class is added; when falsy, the class is removed.
5858+5959+Class names with hyphens or spaces must be quoted. The binding preserves existing classes not managed by Volt.js.
6060+6161+## Event Bindings
6262+6363+### Event Listeners
6464+6565+The `data-volt-on-*` syntax attaches event listeners where the wildcard is the event name:
6666+6767+```html
6868+<button data-volt-on-click="handleClick">Click me</button>
6969+<input data-volt-on-input="query.set($event.target.value)">
7070+<form data-volt-on-submit="handleSubmit($event)">
7171+```
7272+7373+Event handlers receive two special scope variables:
7474+7575+- `$event`: The native browser event object
7676+- `$el`: The element that has the binding
7777+7878+Event expressions commonly call functions or set signal values. The event's default behavior can be prevented by calling `$event.preventDefault()` in the expression.
7979+8080+### Supported Events
8181+8282+Any valid DOM event name works:
8383+8484+- Mouse: `click`, `dblclick`, `mousedown`, `mouseup`, `mouseenter`, `mouseleave`, `mousemove`
8585+- Keyboard: `keydown`, `keyup`, `keypress`
8686+- Form: `input`, `change`, `submit`, `focus`, `blur`
8787+- Touch: `touchstart`, `touchmove`, `touchend`
8888+- Drag: `dragstart`, `dragover`, `drop`
8989+- Media: `play`, `pause`, `ended`, `timeupdate`
9090+9191+Event names are case-insensitive in HTML but case-sensitive in XHTML. Use lowercase for consistency.
9292+9393+## Form Bindings
9494+9595+### Two-Way Binding
9696+9797+The `data-volt-model` binding creates two-way synchronization between form inputs and signals:
9898+9999+```html
100100+<input data-volt-model="username">
101101+<textarea data-volt-model="bio"></textarea>
102102+<select data-volt-model="country">
103103+ <option value="us">United States</option>
104104+ <option value="uk">United Kingdom</option>
105105+</select>
106106+```
107107+108108+The binding works with text inputs, textareas, select dropdowns, checkboxes, and radio buttons.
109109+110110+For checkboxes, the signal value is a boolean. For radio buttons, all inputs with the same `data-volt-model` should share the same signal, and each input should have a unique `value` attribute.
111111+112112+The binding listens for `input` events and updates the signal with the current value. It also sets the initial value from the signal when mounting.
113113+114114+### Checkbox Arrays
115115+116116+Multiple checkboxes can bind to an array signal:
117117+118118+```html
119119+<input type="checkbox" value="red" data-volt-model="colors">
120120+<input type="checkbox" value="green" data-volt-model="colors">
121121+<input type="checkbox" value="blue" data-volt-model="colors">
122122+```
123123+124124+When checked, the checkbox value is added to the array. When unchecked, it's removed. The signal must be initialized as an array.
125125+126126+## Conditional Rendering
127127+128128+### If/Else
129129+130130+The `data-volt-if` binding conditionally renders elements based on expression truthiness:
131131+132132+```html
133133+<p data-volt-if="isLoggedIn">Welcome back!</p>
134134+<p data-volt-else>Please log in.</p>
135135+```
136136+137137+When the condition is falsy, the element is removed from the DOM entirely. When truthy, it's inserted back.
138138+139139+The `data-volt-else` binding must immediately follow a `data-volt-if` sibling element. It renders when the preceding `if` condition is falsy.
140140+141141+Removed elements and their children are completely disposed, including event listeners and nested bindings. This prevents memory leaks and ensures clean teardown.
142142+143143+### Show/Hide Alternative
144144+145145+For toggling visibility without removing elements from the DOM, use `data-volt-class` with a `hidden` class:
146146+147147+```html
148148+<style>
149149+ .hidden { display: none; }
150150+</style>
151151+<p data-volt-class="{ hidden: !isVisible }">Toggle me</p>
152152+```
153153+154154+This approach is more performant for frequently toggled content since elements remain in the DOM.
155155+156156+## List Rendering
157157+158158+### For Loop
159159+160160+The `data-volt-for` binding repeats elements for each item in an array:
161161+162162+```html
163163+<ul>
164164+ <li data-volt-for="item in items" data-volt-text="item.name"></li>
165165+</ul>
166166+```
167167+168168+The syntax is `item in array` where `item` is the loop variable name and `array` is an expression that resolves to an array.
169169+170170+Each iteration creates a new scope with:
171171+172172+- `item`: The current array element
173173+- `$index`: The zero-based index (number)
174174+175175+The binding tracks array changes and efficiently updates the DOM:
176176+177177+- New items are appended
178178+- Removed items are disposed
179179+- Reordered items are moved
180180+181181+For optimal performance with large lists, ensure array items have stable identities. Mutating the array in place triggers re-renders for affected items only.
182182+183183+### Nested Loops
184184+185185+Loops can be nested to render multidimensional data:
186186+187187+```html
188188+<div data-volt-for="category in categories">
189189+ <h2 data-volt-text="category.name"></h2>
190190+ <ul>
191191+ <li data-volt-for="product in category.products" data-volt-text="product.name"></li>
192192+ </ul>
193193+</div>
194194+```
195195+196196+Each loop creates its own scope. Inner loops can access outer loop variables.
197197+198198+### Index Access
199199+200200+Use the `$index` variable to access the current iteration index:
201201+202202+```html
203203+<ul>
204204+ <li data-volt-for="item in items">
205205+ <span data-volt-text="$index + 1"></span>: <span data-volt-text="item.name"></span>
206206+ </li>
207207+</ul>
208208+```
209209+210210+## HTTP
211211+212212+HTTP bindings enable declarative AJAX requests without writing JavaScript. They integrate with hypermedia patterns for server-rendered HTML fragments.
213213+214214+### HTTP Methods
215215+216216+Each HTTP method has a corresponding binding:
217217+218218+```html
219219+<button data-volt-get="/api/users">Fetch Users</button>
220220+<form data-volt-post="/api/users">...</form>
221221+<button data-volt-put="/api/users/1">Update</button>
222222+<button data-volt-patch="/api/users/1">Patch</button>
223223+<button data-volt-delete="/api/users/1">Delete</button>
224224+```
225225+226226+The binding value is the URL. When the element is activated (clicked for buttons, submitted for forms), the request is sent.
227227+228228+### Target and Swap
229229+230230+Control where and how the response HTML is inserted using `data-volt-target` and `data-volt-swap`:
231231+232232+```html
233233+<button
234234+ data-volt-get="/partials/content"
235235+ data-volt-target="#main"
236236+ data-volt-swap="innerHTML">
237237+ Load Content
238238+</button>
239239+```
240240+241241+**Target** is a CSS selector identifying the element to update. If omitted, the element with the HTTP binding is the target.
242242+243243+**Swap** strategies determine how content is inserted:
244244+245245+- `innerHTML`: Replace target's content (default)
246246+- `outerHTML`: Replace target itself
247247+- `beforebegin`: Insert before target
248248+- `afterbegin`: Insert as target's first child
249249+- `beforeend`: Insert as target's last child
250250+- `afterend`: Insert after target
251251+- `delete`: Remove target from DOM
252252+- `none`: Make request but don't modify DOM
253253+254254+### Form Serialization
255255+256256+Forms with HTTP bindings automatically serialize input values:
257257+258258+```html
259259+<form data-volt-post="/api/login">
260260+ <input name="username" data-volt-model="username">
261261+ <input name="password" type="password" data-volt-model="password">
262262+ <button type="submit">Login</button>
263263+</form>
264264+```
265265+266266+The framework serializes form data based on the HTTP method:
267267+268268+- GET/DELETE: Query parameters in URL
269269+- POST/PUT/PATCH: Request body as `application/x-www-form-urlencoded` or `multipart/form-data` for file uploads
270270+271271+### Loading Indicators
272272+273273+Show loading states during requests with `data-volt-indicator`:
274274+275275+```html
276276+<button data-volt-get="/api/data" data-volt-indicator="#spinner">
277277+ Load Data
278278+</button>
279279+<div id="spinner" class="hidden">Loading...</div>
280280+```
281281+282282+The indicator element (selected by CSS selector) has a `loading` class added during the request and removed when complete.
283283+284284+### Retry Logic
285285+286286+Enable automatic retry with exponential backoff using `data-volt-retry`:
287287+288288+```html
289289+<button
290290+ data-volt-get="/api/unreliable"
291291+ data-volt-retry="3">
292292+ Fetch with Retry
293293+</button>
294294+```
295295+296296+The binding value is the maximum number of retry attempts. The framework automatically retries failed requests with increasing delays (1s, 2s, 4s, etc.).
297297+298298+Retries only occur for network failures and 5xx server errors. Client errors (4xx) are not retried.
299299+300300+## Plugins
301301+302302+Plugins extend the framework with additional bindings. Register plugins before mounting to make their bindings available.
303303+304304+### Persist
305305+306306+The `data-volt-persist` binding synchronizes signals with browser storage:
307307+308308+```html
309309+<div
310310+ data-volt
311311+ data-volt-state='{"theme": "light"}'
312312+ data-volt-persist:theme="localStorage">
313313+</div>
314314+```
315315+316316+The binding syntax is `data-volt-persist:signalName="storageType"` where storage type is:
317317+318318+- `localStorage`: Persists across browser sessions
319319+- `sessionStorage`: Persists for the current tab session
320320+- `indexedDB`: For large datasets (async)
321321+322322+The signal value is serialized to JSON and stored. On mount, stored values override initial state.
323323+324324+### Scroll
325325+326326+Scroll bindings manage scroll position and behavior:
327327+328328+```html
329329+<!-- Restore scroll position on navigation -->
330330+<div data-volt-scroll-restore></div>
331331+332332+<!-- Scroll to element -->
333333+<button data-volt-scroll-to="#target">Scroll to Target</button>
334334+335335+<!-- Scroll spy (add class when in viewport) -->
336336+<section data-volt-scroll-spy="active"></section>
337337+338338+<!-- Smooth scrolling -->
339339+<div data-volt-scroll-smooth></div>
340340+```
341341+342342+**Scroll restore** saves scroll position before navigation and restores it when returning.
343343+344344+**Scroll to** scrolls the viewport to bring the target element into view when activated.
345345+346346+**Scroll spy** adds a class when the element enters the viewport and removes it when leaving.
347347+348348+**Smooth scrolling** enables CSS `scroll-behavior: smooth` for the element.
349349+350350+### URL Synchronization
351351+352352+The `data-volt-url` binding syncs signals with URL query parameters or hash:
353353+354354+```html
355355+<div
356356+ data-volt
357357+ data-volt-state='{"page": 1, "query": ""}'
358358+ data-volt-url:page="query"
359359+ data-volt-url:query="query">
360360+</div>
361361+```
362362+363363+The binding syntax is `data-volt-url:signalName="urlPart"` where URL part is:
364364+365365+- `query`: Sync with query parameter (e.g., `?page=1`)
366366+- `hash`: Sync with URL hash (e.g., `#section`)
367367+368368+Signal changes update the URL, and URL changes (back/forward navigation) update signals. This enables client-side routing without additional libraries.
369369+370370+## Custom Bindings
371371+372372+Register custom bindings for domain-specific behavior using the plugin API:
373373+374374+```js
375375+import { registerPlugin } from '@voltjs/volt';
376376+377377+registerPlugin('tooltip', (ctx) => {
378378+ const message = ctx.evaluate(ctx.element.getAttribute('data-volt-tooltip'));
379379+ const tooltip = document.createElement('div');
380380+ tooltip.className = 'tooltip';
381381+ tooltip.textContent = message;
382382+383383+ ctx.element.addEventListener('mouseenter', () => {
384384+ document.body.appendChild(tooltip);
385385+ });
386386+387387+ ctx.element.addEventListener('mouseleave', () => {
388388+ tooltip.remove();
389389+ });
390390+391391+ ctx.addCleanup(() => tooltip.remove());
392392+});
393393+```
394394+395395+The plugin context provides:
396396+397397+- `element`: The DOM element with the binding
398398+- `scope`: Signal scope for the mounted component
399399+- `evaluate(expression)`: Evaluate expressions in the current scope
400400+- `findSignal(path)`: Find signals by property path
401401+- `addCleanup(fn)`: Register cleanup callbacks
402402+403403+Custom bindings should always register cleanup to prevent memory leaks.
404404+405405+## Lifecycle
406406+407407+All bindings follow a consistent lifecycle:
408408+409409+1. **Mount**: The `charge()` or `mount()` function discovers elements with `data-volt-*` attributes
410410+2. **Evaluation**: Expressions are parsed and evaluated in the current scope
411411+3. **Subscription**: Bindings subscribe to referenced signals
412412+4. **Update**: When signals change, bindings re-evaluate and update the DOM
413413+5. **Cleanup**: When unmounted, subscriptions are disposed and DOM changes are reverted
414414+415415+Cleanup is automatic for all built-in bindings. Custom bindings must explicitly register cleanup using `ctx.addCleanup()`.
+170
docs/installation.md
···11+# Installation
22+33+<!-- TODO: Figure out actual project path @voltjs/volt -->
44+55+Volt.js can be installed via CDN or package manager. Choose the method that best fits your project setup.
66+77+## CDN (unpkg)
88+99+The simplest way to get started is loading Volt.js directly from a CDN. This approach requires no build tools and works immediately in any HTML file.
1010+1111+### ES Modules
1212+1313+Use the module build for modern browsers with ES module support:
1414+1515+```html
1616+<script type="module">
1717+ import { charge, registerPlugin } from 'https://unpkg.com/@voltjs/volt@latest/dist/volt.js';
1818+ charge();
1919+</script>
2020+```
2121+2222+You can optionally pin to a specific version:
2323+2424+```html
2525+<script type="module">
2626+ import { charge } from 'https://unpkg.com/@voltjs/volt@0.1.0/dist/volt.js';
2727+ charge();
2828+</script>
2929+```
3030+3131+## Package Manager
3232+3333+For applications using node based tools, install Volt.js via npm or similar:
3434+3535+```bash
3636+npm install @voltjs/volt
3737+```
3838+3939+```bash
4040+pnpm add @voltjs/volt
4141+```
4242+4343+### Module Imports
4444+4545+Import only the functions you need to minimize bundle size:
4646+4747+```js
4848+import { charge, registerPlugin } from '@voltjs/volt';
4949+import { persistPlugin } from '@voltjs/volt/plugins';
5050+5151+registerPlugin('persist', persistPlugin);
5252+charge();
5353+```
5454+5555+The framework uses tree-shaking to eliminate unused code when bundled with modern build tools like Vite, Rollup, or Webpack.
5656+5757+## TypeScript
5858+5959+Volt.js is written in TypeScript and includes complete type definitions.
6060+6161+TypeScript users get automatic type inference for:
6262+6363+- Signal values and methods
6464+- Computed dependencies
6565+- Plugin contexts
6666+- Scope objects passed to `mount()`
6767+6868+## Basic Setup
6969+7070+### Declarative Mode (Recommended)
7171+7272+For applications that can be built entirely in HTML, use the declarative approach with `charge()`:
7373+7474+```html
7575+<!DOCTYPE html>
7676+<html lang="en">
7777+<head>
7878+ <meta charset="UTF-8">
7979+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
8080+ <title>Volt.js App</title>
8181+</head>
8282+<body>
8383+ <div data-volt data-volt-state='{"count": 0}'>
8484+ <h1 data-volt-text="count">0</h1>
8585+ <button data-volt-on-click="count.set(count.get() + 1)">Increment</button>
8686+ </div>
8787+8888+ <script type="module">
8989+ import { charge } from 'https://unpkg.com/@voltjs/volt@latest/dist/volt.js';
9090+ charge();
9191+ </script>
9292+</body>
9393+</html>
9494+```
9595+9696+The `charge()` function auto-discovers all elements with the `data-volt` attribute and mounts them with their declared state.
9797+9898+### Programmatic Mode
9999+100100+For applications requiring initialization logic, use the programmatic API with `mount()`:
101101+102102+```html
103103+<script type="module">
104104+ import { mount, signal } from 'https://unpkg.com/@voltjs/volt@latest/dist/volt.js';
105105+106106+ const count = signal(0);
107107+108108+ mount(document.querySelector('#app'), {
109109+ count,
110110+ increment: () => count.set(count.get() + 1)
111111+ });
112112+</script>
113113+```
114114+115115+This approach gives you full control over signal creation, initialization, and the scope object.
116116+117117+## Server-Side Rendering
118118+119119+For SSR applications, use the `hydrate()` function instead of `charge()` to preserve server-rendered HTML and attach interactivity:
120120+121121+```html
122122+<script type="module">
123123+ import { hydrate } from '@voltjs/volt';
124124+ hydrate();
125125+</script>
126126+```
127127+128128+See the [Server-Side Rendering & Lifecycle](./lifecycle) documentation for complete SSR patterns and hydration strategies.
129129+130130+## Plugin Setup
131131+132132+Volt.js includes several built-in plugins that must be registered before use:
133133+134134+```html
135135+<script type="module">
136136+ import { charge, registerPlugin } from '@voltjs/volt';
137137+ import { persistPlugin, scrollPlugin, urlPlugin } from '@voltjs/volt/plugins';
138138+139139+ registerPlugin('persist', persistPlugin);
140140+ registerPlugin('scroll', scrollPlugin);
141141+ registerPlugin('url', urlPlugin);
142142+143143+ charge();
144144+</script>
145145+```
146146+147147+Register plugins before calling `charge()`, `mount()`, or `hydrate()` to ensure they're available for binding resolution.
148148+149149+## Browser Compatibility
150150+151151+Volt.js requires modern browsers with support for:
152152+153153+- ES2020 syntax (optional chaining, nullish coalescing)
154154+- ES modules
155155+- Proxy objects
156156+- CSS custom properties
157157+158158+**Minimum versions:**
159159+160160+- Chrome 90+
161161+- Firefox 88+
162162+- Safari 14+
163163+- Edge 90+
164164+165165+## Next Up
166166+167167+- Read the [Framework Overview](./overview) to understand core concepts
168168+- Learn about [State Management](./state) with signals and computed values
169169+- Explore available [Bindings](./bindings) for DOM manipulation
170170+- Check out [Expression Evaluation](./expressions) for template syntax
+129
docs/state.md
···11+# State Management
22+33+Volt.js uses signal-based reactivity for state management. State changes automatically trigger DOM updates without virtual DOM diffing or reconciliation.
44+55+## Reactive Primitives
66+77+### Signals
88+99+Signals are the foundation of reactive state. A signal holds a single value that can be read, written, and observed for changes.
1010+1111+Create signals using the `signal()` function, which returns an object with three methods:
1212+1313+- `get()` returns the current value
1414+- `set(newValue)` updates the value and notifies subscribers
1515+- `subscribe(callback)` registers a listener for changes
1616+1717+Signals use strict equality (`===`) to determine if a value has changed. Setting a signal to its current value will not trigger notifications.
1818+1919+### Computed Values
2020+2121+Computed signals derive their values from other signals. They automatically track dependencies and recalculate only when those dependencies change.
2222+2323+The `computed()` function takes a calculation function and a dependency array. The framework ensures computed values stay synchronized with their sources.
2424+2525+Computed values are read-only and should not produce side effects. They exist purely to transform or combine other state.
2626+2727+### Effects
2828+2929+Effects run side effects in response to signal changes. The `effect()` function executes immediately and re-runs whenever its dependencies update.
3030+3131+Common uses include:
3232+3333+- Synchronizing with external APIs
3434+- Logging or analytics
3535+- Coordinating multiple signals
3636+3737+For asynchronous operations, use `asyncEffect()` which handles cleanup of pending operations when dependencies change or the effect is disposed.
3838+3939+## Declarative State
4040+4141+The preferred approach for most applications is declaring state directly in HTML using the `data-volt-state` attribute. This eliminates the need to write JavaScript for basic state management.
4242+4343+State is declared as inline JSON on any element with the `data-volt` attribute:
4444+4545+```html
4646+<div data-volt data-volt-state='{"count": 0, "items": []}'>
4747+```
4848+4949+The framework automatically converts these values into reactive signals. Nested objects and arrays become reactive, and property access in expressions automatically unwraps signal values.
5050+5151+### Computed Values in Markup
5252+5353+Derive values declaratively using `data-volt-computed:name` attributes. The name becomes a signal in the scope, and the attribute value is the computation expression:
5454+5555+```html
5656+<div data-volt
5757+ data-volt-state='{"count": 5}'
5858+ data-volt-computed:doubled="count * 2">
5959+```
6060+6161+Computed values defined this way follow the same rules as programmatic computed signalsthey track dependencies and update automatically.
6262+6363+## Programmatic State
6464+6565+For complex applications requiring initialization logic or external API integration, create signals programmatically and pass them to the `mount()` function.
6666+6767+This approach gives you full control over signal creation, composition, and lifecycle. Use it when:
6868+6969+- State initialization requires async operations
7070+- Signals need to be shared across multiple mount points
7171+- Complex validation or transformation logic is needed
7272+- Integration with external state management is required
7373+7474+## Scope and Access
7575+7676+Each mounted element creates a scope containing its signals and computed values. Bindings access signals by property path relative to their scope.
7777+7878+When using declarative state, the scope is built automatically from `data-volt-state` and `data-volt-computed:*` attributes.
7979+8080+When using programmatic mounting, the scope is the object passed as the second argument to `mount()`.
8181+8282+Bindings can access nested properties, and the evaluator automatically unwraps signal values. Event handlers receive special scope additions: `$el` for the element and `$event` for the event object.
8383+8484+## Signal Methods in Expressions
8585+8686+While signal values are automatically unwrapped in most expressions, explicit signal methods are available when needed:
8787+8888+- Use `signal.get()` to read the current value
8989+- Use `signal.set(newValue)` to update state from event handlers
9090+- Use `signal.subscribe(fn)` in custom JavaScript (not typical in templates)
9191+9292+The `.set()` method is commonly used in `data-volt-on-*` event bindings to update state in response to user actions.
9393+9494+## State Persistence
9595+9696+Signals can be synchronized with browser storage using the built-in persist plugin. See the plugin documentation for details on localStorage, sessionStorage, and IndexedDB integration.
9797+9898+## State Serialization
9999+100100+For server-side rendering, signals can be serialized to JSON and embedded in HTML for hydration on the client. This preserves state across the server-client boundary.
101101+102102+Only serialize base signals containing primitive values, arrays, and plain objects. Computed signals are recalculated during hydration and should not be serialized.
103103+104104+See the [Server-Side Rendering & Lifecycle](./lifecycle.md) documentation for complete SSR patterns.
105105+106106+## Guidelines
107107+108108+### Performance
109109+110110+- Keep signal values immutable when possible. Create new objects rather than mutating existing ones
111111+- Use computed signals to avoid redundant calculations
112112+- Avoid creating signals inside loops or frequently-called functions
113113+114114+### Architecture
115115+116116+- Prefer declarative state for simple, self-contained components
117117+- Use programmatic state for complex initialization or cross-component coordination
118118+- Keep state close to where it's used: avoid deeply nested property access
119119+- Structure state with consistent shapes to prevent runtime errors in expressions
120120+121121+### Debugging
122122+123123+Signal updates are synchronous and deterministic. To trace state changes:
124124+125125+- Use browser DevTools to set breakpoints in signal `.set()` calls
126126+- Subscribe to signals and log changes for debugging
127127+- Enable Volt.js lifecycle hooks to observe mount and binding creation
128128+129129+All errors in effects and subscriptions are caught and logged rather than thrown, preventing cascade failures.
+289
docs/usage/counter.md
···11+# Building a Counter
22+33+This tutorial walks through building a simple counter application to demonstrate Volt.js fundamentals: reactive state, event handling, computed values, and declarative markup.
44+55+## Basic Counter (Declarative)
66+77+The simplest way to build a counter is using declarative state and bindings directly in HTML.
88+99+Create an HTML file with this structure:
1010+1111+```html
1212+<!DOCTYPE html>
1313+<html lang="en">
1414+<head>
1515+ <meta charset="UTF-8">
1616+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
1717+ <title>Counter - Volt.js</title>
1818+</head>
1919+<body>
2020+ <div data-volt data-volt-state='{"count": 0}'>
2121+ <h1 data-volt-text="count">0</h1>
2222+ <button data-volt-on-click="count.set(count.get() + 1)">Increment</button>
2323+ </div>
2424+2525+ <script type="module">
2626+ import { charge } from 'https://unpkg.com/@voltjs/volt@latest/dist/volt.js';
2727+ charge();
2828+ </script>
2929+</body>
3030+</html>
3131+```
3232+3333+**How it works:**
3434+3535+The `data-volt` attribute marks the root element for mounting. Inside, `data-volt-state` declares initial state as inline JSON.
3636+The framework converts `count` into a reactive signal automatically.
3737+3838+The `data-volt-text` binding displays the current count value. When the signal changes, the text content updates automatically.
3939+4040+The `data-volt-on-click` binding attaches a click handler that increments the count. We call `count.get()` to read the current value and `count.set()` to update it.
4141+4242+Finally, `charge()` discovers all `[data-volt]` elements and mounts them with their declared state.
4343+4444+## Adding Decrement
4545+4646+Extend the counter with both increment and decrement buttons:
4747+4848+```html
4949+<div data-volt data-volt-state='{"count": 0}'>
5050+ <h1 data-volt-text="count">0</h1>
5151+ <button data-volt-on-click="count.set(count.get() - 1)">-</button>
5252+ <button data-volt-on-click="count.set(count.get() + 1)">+</button>
5353+</div>
5454+```
5555+5656+Each button calls `count.set()` with a different expression. The decrement button subtracts 1, while increment adds 1.
5757+5858+## Computed Values
5959+6060+Add derived state using `data-volt-computed` to show whether the count is positive, negative, or zero:
6161+6262+```html
6363+<div data-volt
6464+ data-volt-state='{"count": 0}'
6565+ data-volt-computed:status="count > 0 ? 'positive' : count < 0 ? 'negative' : 'zero'">
6666+ <h1 data-volt-text="count">0</h1>
6767+ <p>Status: <span data-volt-text="status">zero</span></p>
6868+6969+ <button data-volt-on-click="count.set(count.get() - 1)">-</button>
7070+ <button data-volt-on-click="count.set(count.get() + 1)">+</button>
7171+</div>
7272+```
7373+7474+The `data-volt-computed:status` attribute creates a computed signal named `status`. It uses a ternary expression to classify the count. When `count` changes, `status` recalculates automatically.
7575+7676+## Conditional Rendering
7777+7878+Show different messages based on the count value using conditional bindings:
7979+8080+```html
8181+<div data-volt data-volt-state='{"count": 0}'>
8282+ <h1 data-volt-text="count">0</h1>
8383+8484+ <p data-volt-if="count === 0">The count is zero</p>
8585+ <p data-volt-if="count > 0" data-volt-text="'Positive: ' + count"></p>
8686+ <p data-volt-if="count < 0" data-volt-text="'Negative: ' + count"></p>
8787+8888+ <button data-volt-on-click="count.set(count.get() - 1)">-</button>
8989+ <button data-volt-on-click="count.set(count.get() + 1)">+</button>
9090+ <button data-volt-on-click="count.set(0)">Reset</button>
9191+</div>
9292+```
9393+9494+The `data-volt-if` binding conditionally renders elements. Only one paragraph displays at a time based on the count value. A reset button sets the count back to zero.
9595+9696+## Styling with Classes
9797+9898+Apply dynamic CSS classes based on state:
9999+100100+```html
101101+<style>
102102+ .counter {
103103+ padding: 2rem;
104104+ text-align: center;
105105+ font-family: system-ui, sans-serif;
106106+ }
107107+108108+ .display {
109109+ font-size: 4rem;
110110+ margin: 1rem 0;
111111+ }
112112+113113+ .positive { color: #22c55e; }
114114+ .negative { color: #ef4444; }
115115+ .zero { color: #6b7280; }
116116+117117+ button {
118118+ font-size: 1.5rem;
119119+ padding: 0.5rem 1.5rem;
120120+ margin: 0.25rem;
121121+ cursor: pointer;
122122+ }
123123+</style>
124124+```
125125+126126+```html
127127+<div class="counter"
128128+ data-volt
129129+ data-volt-state='{"count": 0}'>
130130+ <h1 class="display"
131131+ data-volt-text="count"
132132+ data-volt-class="{ positive: count > 0, negative: count < 0, zero: count === 0 }">
133133+ 0
134134+ </h1>
135135+136136+ <div>
137137+ <button data-volt-on-click="count.set(count.get() - 1)">-</button>
138138+ <button data-volt-on-click="count.set(0)">Reset</button>
139139+ <button data-volt-on-click="count.set(count.get() + 1)">+</button>
140140+ </div>
141141+</div>
142142+```
143143+144144+The `data-volt-class` binding takes an object where keys are class names and values are conditions. When `count` is positive, the `positive` class applies. When negative, the `negative` class applies. When zero, the `zero` class applies.
145145+146146+## Persisting State
147147+148148+Use the persist plugin to save the count across page reloads:
149149+150150+```html
151151+<div data-volt
152152+ data-volt-state='{"count": 0}'
153153+ data-volt-persist:count="localStorage">
154154+ <h1 data-volt-text="count">0</h1>
155155+156156+ <button data-volt-on-click="count.set(count.get() - 1)">-</button>
157157+ <button data-volt-on-click="count.set(0)">Reset</button>
158158+ <button data-volt-on-click="count.set(count.get() + 1)">+</button>
159159+</div>
160160+161161+<script type="module">
162162+ import { charge, registerPlugin } from 'https://unpkg.com/@voltjs/volt@latest/dist/volt.js';
163163+ import { persistPlugin } from 'https://unpkg.com/@voltjs/volt@latest/dist/plugins.js';
164164+165165+ registerPlugin('persist', persistPlugin);
166166+ charge();
167167+</script>
168168+```
169169+170170+The `data-volt-persist:count="localStorage"` binding synchronizes the `count` signal with browser localStorage. When the count changes, it's saved automatically. When the page loads, the saved value is restored.
171171+172172+## Step Counter
173173+174174+Build a counter that increments by a configurable step value:
175175+176176+```html
177177+<div data-volt data-volt-state='{"count": 0, "step": 1}'>
178178+ <h1 data-volt-text="count">0</h1>
179179+180180+ <label>
181181+ Step:
182182+ <input type="number" data-volt-model="step" min="1" value="1">
183183+ </label>
184184+185185+ <div>
186186+ <button data-volt-on-click="count.set(count.get() - step)">-</button>
187187+ <button data-volt-on-click="count.set(0)">Reset</button>
188188+ <button data-volt-on-click="count.set(count.get() + step)">+</button>
189189+ </div>
190190+</div>
191191+```
192192+193193+The `data-volt-model` binding creates two-way synchronization between the input and the `step` signal. As you type, the step value updates. The increment and decrement buttons use the current step value.
194194+195195+## Bounded Counter
196196+197197+Add minimum and maximum bounds with disabled button states:
198198+199199+```html
200200+<div data-volt
201201+ data-volt-state='{"count": 0, "min": -10, "max": 10}'>
202202+ <h1 data-volt-text="count">0</h1>
203203+204204+ <div>
205205+ <button
206206+ data-volt-on-click="count.set(count.get() - 1)"
207207+ data-volt-bind:disabled="count <= min">
208208+ -
209209+ </button>
210210+ <button data-volt-on-click="count.set(0)">Reset</button>
211211+ <button
212212+ data-volt-on-click="count.set(count.get() + 1)"
213213+ data-volt-bind:disabled="count >= max">
214214+ +
215215+ </button>
216216+ </div>
217217+218218+ <p>Range: <span data-volt-text="min"></span> to <span data-volt-text="max"></span></p>
219219+</div>
220220+```
221221+222222+The `data-volt-bind:disabled` binding disables buttons when the count reaches the minimum or maximum. The decrement button disables at the minimum, and the increment button disables at the maximum.
223223+224224+## Programmatic Counter
225225+226226+For applications requiring initialization logic or custom functions, use the programmatic API:
227227+228228+```html
229229+<script type="module">
230230+ import { mount, signal, computed } from 'https://unpkg.com/@voltjs/volt@latest/dist/volt.js';
231231+232232+ const count = signal(0);
233233+ const message = computed(() => {
234234+ const value = count.get();
235235+ if (value === 0) return 'Start counting!';
236236+ if (value > 0) return `Up by ${value}`;
237237+ return `Down by ${Math.abs(value)}`;
238238+ }, [count]);
239239+240240+ const increment = () => {
241241+ count.set(count.get() + 1);
242242+ };
243243+244244+ const decrement = () => {
245245+ count.set(count.get() - 1);
246246+ };
247247+248248+ const reset = () => {
249249+ count.set(0);
250250+ };
251251+252252+ mount(document.querySelector('#app'), {
253253+ count,
254254+ message,
255255+ increment,
256256+ decrement,
257257+ reset
258258+ });
259259+</script>
260260+```
261261+262262+This approach creates signals explicitly using `signal()` and `computed()`. Functions are defined for event handlers and passed to the scope object. The `mount()` function attaches bindings to the element.
263263+264264+Use programmatic mounting when you need:
265265+266266+- Complex initialization logic
267267+- Integration with external libraries
268268+- Signals shared across multiple components
269269+- Custom validation or transformation
270270+271271+## Summary
272272+273273+This counter demonstrates core Volt.js concepts:
274274+275275+- Reactive state with signals
276276+- Event handling with `data-volt-on-*`
277277+- Computed values deriving from state
278278+- Conditional rendering with `data-volt-if`
279279+- Two-way form binding with `data-volt-model`
280280+- Attribute binding with `data-volt-bind:*`
281281+- Dynamic classes with `data-volt-class`
282282+- State persistence with plugins
283283+284284+## Further Reading
285285+286286+- [State Management](../state) for advanced signal patterns
287287+- [Bindings](../bindings) for complete binding reference
288288+- [Expressions](../expressions) for template syntax details
289289+- [Lifecycle](../lifecycle) for SSR and hydration