statedom
v0.1.0
Published
A minimal, fine-grained reactive UI framework
Maintainers
Readme
dom-signals
A minimal, fine-grained reactive UI framework.
Features
- Zero dependencies - No build step required
- Fine-grained reactivity - Automatic dependency tracking via Proxy
- Async native - Built-in support for streams, polling, and cancellation
- Small footprint - 8KB gzipped (core), 28KB gzipped (full framework)
Installation
npm install dom-signalsOr use directly in the browser:
<script type="module">
import { html, render, state } from 'https://unpkg.com/dom-signals'
</script>Quick Start
import { html, render, state } from 'dom-signals'
// Create reactive state
const counter = state({ count: 0 })
// Render with automatic updates
render(html`
<div>
<p>Count: ${() => counter.count}</p>
<button onclick=${() => counter.count++}>Increment</button>
</div>
`, document.body)Core Concepts
Reactive State
import { state, effect } from 'dom-signals'
const user = state({ name: 'Alice', age: 30 })
// Effects run automatically when dependencies change
effect(() => {
console.log(`${user.name} is ${user.age} years old`)
})
user.age = 31 // Logs: "Alice is 31 years old"Templates
import { html } from 'dom-signals'
// Static content
html`<h1>Hello World</h1>`
// Reactive content (wrap in function)
html`<p>Count: ${() => counter.count}</p>`
// Event handlers
html`<button onclick=${handleClick}>Click me</button>`
// Conditional rendering
html`<div>${() => isLoggedIn ? 'Welcome!' : 'Please log in'}</div>`Lists
import { html, each } from 'dom-signals'
const todos = state([
{ id: 1, text: 'Learn dom-signals' },
{ id: 2, text: 'Build something' }
])
html`
<ul>
${each(todos, 'id', (todo) => html`
<li>${todo.text}</li>
`)}
</ul>
`Async Data
import { html, state } from 'dom-signals'
const s = state({ users: [] })
// Fetch with automatic status tracking
s.query('users', async () => {
const res = await fetch('/api/users')
return res.json()
})
// Access status: 'idle' | 'pending' | 'loaded' | 'error'
html`
<div>
${() => s.$status.users === 'pending'
? 'Loading...'
: s.users.map(u => u.name).join(', ')
}
</div>
`Routing
import { router, html } from 'dom-signals'
router('#app', {
'/': { component: HomePage },
'/users/:id': {
loader: ({ params }) => fetch(`/api/users/${params.id}`),
component: UserPage
},
'*': { component: NotFound }
})API Reference
Reactivity
state(init)- Create reactive state objecteffect(fn)- Run function when dependencies changebatch(fn)- Batch multiple updatescontext(name)- Create/access named context
Templates
html- Tagged template for creating DOMrender(template, container)- Mount template to DOM
Helpers
each(items, key, fn)- Render keyed listsmatch(value, cases)- Pattern matchingbind(state, prop)- Two-way bindingstyle(obj)/classes(obj)- Dynamic styling
Async
s.query(key, asyncFn)- Track async operations with automatic statuss.$status.key- Access loading state ('idle' | 'pending' | 'loaded' | 'error')s.$error.key- Access error object if status is 'error'
Routing
router(selector, routes, options)- Client-side routingapp(selector, config)- Full app helper with routing
Browser Support
ES Modules + Proxy support:
- Chrome 61+
- Firefox 60+
- Safari 11+
- Edge 79+
License
MIT
