Quick Start

The WildflowerJS model in under a minute. You define state, bind it to the DOM, and the framework updates the page when the state changes.

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. There are no classes or constructors, only 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" calls increment(), which sets this.count++
  • Proxy detects the mutation: WildflowerJS wraps state in a deep reactive Proxy. The assignment to count is intercepted automatically.
  • Only that text node updates: the framework knows which DOM node is bound to count and patches it directly, with no diffing and no virtual DOM.
You write normal JavaScript assignments like this.count++, and the framework turns each one into a targeted DOM update.

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.

Required: 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 batches synchronous mutations. All three assignments are collected and flushed as a single render pass, so the intermediate states never reach the screen.

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 = ''
        }
    }
})
Live Preview
Nano Counter

Reactive state updates with computed properties, two-way model binding, and a configurable step size.

Next Steps

You now know the core model. State drives the DOM through bindings, and the Proxy intercepts each assignment so the framework can update the elements bound to it. 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