Quick Start
Learn the WildflowerJS mental model in under a minute: define state, bind it to the DOM, and watch reactivity handle the rest.
1. Plain State
Every component starts with a state object. This is the single source of truth for your UI:
wildflower.component('counter', {
state: {
count: 0
}
})
That's it. No classes, no constructors. Just data.
2. Bind to the DOM
Connect state to HTML with data-bind, and wire up interactions with data-action:
<div data-component="counter">
<p>Count: <span data-bind="count">0</span></p>
<button data-action="increment">+1</button>
</div>
wildflower.component('counter', {
state: {
count: 0
},
increment() {
this.count++
}
})
What Happens When You Click
- Click runs the method:
data-action="increment"callsincrement(), which setsthis.count++ - Proxy detects the mutation: WildflowerJS wraps state in a deep reactive Proxy. The assignment to
countis intercepted automatically. - Only that text node updates: the framework knows which DOM node is bound to
countand patches it directly. No diffing, no virtual DOM.
this.count++). The framework turns them into targeted DOM updates behind the scenes.
3. Computed Values
Derive new values from state. Computed properties track their dependencies automatically and recalculate only when those dependencies change:
<div data-component="counter">
<p>Count: <span data-bind="count">0</span></p>
<p>Doubled: <span data-bind="count * 2">0</span></p>
<p>Is even: <span data-bind="isEven"></span></p>
<button data-action="increment">+1</button>
</div>
wildflower.component('counter', {
state: {
count: 0
},
computed: {
isEven() {
return this.count % 2 === 0 ? 'Yes' : 'No'
}
},
increment() {
this.count++
}
})
Two ways to derive values:
- Inline expressions:
data-bind="count * 2"for simple math or logic, evaluated directly in the binding - Computed properties:
isEven()for anything more complex. Results are cached until dependencies change.
4. Lists
Render arrays with data-list. Each item in the array gets its own copy of the <template>:
<div data-component="grocery-list">
<ul data-list="items">
<template>
<li data-bind="name"></li>
</template>
</ul>
<button data-action="addItem">Add Milk</button>
</div>
wildflower.component('grocery-list', {
state: {
items: [
{ name: 'Eggs' },
{ name: 'Bread' }
]
},
addItem() {
this.items.push({ name: 'Milk' })
}
})
Inside the template, data-bind="name" refers to each item's name property. Array mutations like push, splice, and filter are all reactive.
data-list must have a <template> child. The template defines the markup for each item.
5. Conditionals
Show or hide elements based on state with data-show:
<div data-component="grocery-list">
<p data-show="items.length > 0">
You have <span data-bind="items.length"></span> items.
</p>
<p data-show="items.length === 0">Your list is empty.</p>
<ul data-list="items">
<template>
<li data-bind="name"></li>
</template>
</ul>
</div>
data-show accepts any expression that evaluates to truthy or falsy. The element stays in the DOM but is hidden with display: none when the condition is false. For full add/remove from the DOM, use data-render instead.
6. Batching
Multiple state mutations in a single method produce one DOM update, not three:
wildflower.component('profile', {
state: {
firstName: '',
lastName: '',
status: 'idle'
},
loadUser() {
this.firstName = 'Jane' // mutation 1
this.lastName = 'Doe' // mutation 2
this.status = 'loaded' // mutation 3
// --> one DOM update at the end
}
})
WildflowerJS automatically batches synchronous mutations. All three assignments are collected and flushed as a single render pass. This means you never need to worry about intermediate states causing unnecessary repaints.
Putting It Together
Here is a working example that uses every concept from this page. Try it live:
<div data-component="task-tracker-demo">
<h5>Tasks</h5>
<p>Total: <span data-bind="tasks.length"></span>
| Done: <span data-bind="doneCount"></span></p>
<div class="d-flex gap-2 mb-2">
<input type="text" class="form-control form-control-sm"
data-model="newTitle" placeholder="New task...">
<button class="btn btn-primary btn-sm" data-action="addTask">Add</button>
</div>
<p data-show="tasks.length === 0">
<em>No tasks yet.</em>
</p>
<ul data-list="tasks" class="list-unstyled">
<template>
<li class="mb-1">
<input type="checkbox" data-model="done">
<span data-bind="title" class="ms-1"
data-bind-class="done ? 'text-muted text-decoration-line-through' : ''"></span>
</li>
</template>
</ul>
</div>
wildflower.component('task-tracker-demo', {
state: {
tasks: [
{ title: 'Learn WildflowerJS', done: false },
{ title: 'Build something', done: false }
],
newTitle: ''
},
computed: {
doneCount() {
return this.tasks.filter(t => t.done).length
}
},
addTask() {
const title = this.newTitle.trim()
if (title) {
this.tasks.push({ title, done: false })
this.newTitle = ''
}
}
})
Next Steps
You now know the core mental model: state drives the DOM through bindings, and the Proxy system handles the rest. From here:
- Components: Nested components, slots, and communication patterns
- State: Nested objects, arrays, and state organization
- Stores: Shared state across components
- Forms: Two-way binding with
data-model