Installation
WildflowerJS installs with one script tag. There are no build tools, compilation steps, or configuration files.
- No build tools or compilation
- No package.json complexity
- Works directly in browsers
- Standards-compliant HTML
Distribution Bundles
WildflowerJS provides pre-built bundles for different use cases:
| Bundle | Includes | Use Case |
|---|---|---|
wildflower.nano.min.js |
Core + Stores (no data-list, data-pools, plugins, portals, transitions, modals) | Smallest footprint; interactive widgets and single-file artifacts (data-bind, data-show, data-render, data-model, forms, computed, external) |
wildflower.mini-pool.min.js |
Core + Stores + data-pool (no data-list, plugins, portals, transitions, modals) | Games, simulations, and per-frame visualization that render through pools |
wildflower.mini.min.js |
Core + Stores + data-list (no data-pools, plugins, portals, transitions, modals) | Small footprint with list rendering (standard CRUD UI, forms, dashboards) |
wildflower.lite.min.js |
Core + Stores + data-list + data-pools (no plugins, portals, transitions, modals) | Minimal footprint needing both rendering primitives, lists and high-frequency entities |
wildflower.min.js |
Core + Stores + Plugins + Portals + Transitions + Modals | Most applications |
wildflower.spa.min.js |
Core + Stores + All Features + Router | Single-page applications |
wildflower.full.min.js |
Core + Stores + All Features + Router + SSR + Data queries | SEO/SSR applications, and anything reading from a server |
Development builds (.dev.js) include all console messages. Production builds (.min.js) strip debug logging but keep error messages.
One demo per bundle:
A complete interactive widget in one HTML file. Computed totals, two-way inputs, reactive styling, and a conditional message.
Reactive lists with input binding, filtering, item actions, and computed counts. This is what data-list adds over Nano.
800+ autonomous agents at native frame rate. Spatial grid neighbor search, predator/prey dynamics, and canvas trails. Every boid is a DOM element.
Full-featured kanban with drag-and-drop, modals, animated transitions, search filtering, and localStorage persistence.
Complete SPA with client-side routing, product catalog, shopping cart, checkout flow, and toast notifications.
What the Nano build includes
Nano is the smallest bundle, and it is still a complete reactive framework. Those ~51 KB (brotli) cover everything you need to ship a real interactive widget, rather than a bare signals primitive you have to build the rest around:
- Components with a full lifecycle (
init,destroy, update hooks) - Reactive state and computed properties, backed by a real dependency graph
- Two-way binding (
data-model) for inputs and forms - Conditionals:
data-showto toggle visibility,data-renderto insert and remove elements - Form handling with validation
- Error boundaries, so one broken component fails in place instead of taking down the page
- Stores for shared state, plus
external()to read across them - Expression evaluation, including a CSP-safe mode for strict environments
data-bind-class,data-bind-style, anddata-bind-attr, with attribute sanitization that blockson*handlers andjavascript:URLs- Directives and hooks for your own extensions
- WildQuery, the built-in jQuery-style DOM helper
Why 48 KB and not 5 KB? Because that list is what a widget needs once it leaves a demo. Form validation, error isolation, XSS-safe attribute writes, and a shared-state layer stop being optional the moment someone starts typing into your widget. Nano includes all of them and nothing above them.
Nano and Mini differ by one feature, list rendering (data-list). The tiers above Mini add entity pools, portals, transitions, modals, plugins, routing, and server-side rendering. Use Mini when you want a declarative collection that stays in sync with an array. Nano already includes everything below that point, and you can still render a collection by hand when you need one (below).
Rendering a collection without data-list
Keep the array in state, write one item's markup once in a standard HTML <template>, and clone it on add. Appending the element is enough to initialize it, and removing it is enough to tear it down. The same mutation observer that detects dynamic components handles both, so there is no scan() or destroy call to remember.
<div id="wall"></div>
<template id="row-template">
<div data-component="clock-row">
<span data-bind="label"></span>
<button data-action="remove">Remove</button>
</div>
</template>
// Add an item: clone the template, append it, done.
addRow(value) {
this.rows = this.rows.concat(value); // array in state
const el = document.getElementById('row-template')
.content.firstElementChild.cloneNode(true);
el.dataset.value = value;
document.getElementById('wall').appendChild(el); // initialized automatically
}
// Inside the clock-row component, tear down on click:
remove() { this.element.remove(); } // destroyed automatically; destroy() runs
This pattern works in every build.
Builds its add/remove city wall by cloning a plain <template>. No data-list.
Installation Options
Script Tag
Add one script tag to your HTML:
<!-- Production (minified, no debug logs) -->
<script src="path/to/wildflower.min.js"></script>
<!-- Development (with debug logs) -->
<script src="path/to/wildflower.dev.js"></script>
<!-- Or enable debug via script attribute -->
<script src="path/to/wildflower.min.js" data-debug="true"></script>
Script Tag Configuration
Configure WildflowerJS directly from the script tag without any JavaScript:
| Attribute | Values | Default | Description |
|---|---|---|---|
data-debug |
true, "" |
false |
Enable debug mode and verbose logging |
data-error-handling |
log, throw, silent |
log |
How framework errors are handled |
data-auto-init |
true, false |
true |
Automatically initialize on page load |
data-wf-prefix |
true, false |
false |
Only process data-wf-* attributes |
<!-- Enable debug mode -->
<script src="wildflower.min.js" data-debug="true"></script>
<!-- Throw errors instead of logging (useful in development) -->
<script src="wildflower.min.js" data-debug data-error-handling="throw"></script>
<!-- Manual initialization (disable auto-init) -->
<script src="wildflower.min.js" data-auto-init="false"></script>
CDN
<!-- jsDelivr CDN -->
<script src="https://cdn.jsdelivr.net/npm/wildflowerjs@1/dist/wildflower.min.js"></script>
<!-- Smallest tier, for a single-file interactive widget -->
<script src="https://cdn.jsdelivr.net/npm/wildflowerjs@1/dist/wildflower.nano.min.js"></script>
<!-- With SPA routing -->
<script src="https://cdn.jsdelivr.net/npm/wildflowerjs@1/dist/wildflower.spa.min.js"></script>
ES Modules
Every tier also ships as an ES module (from 1.5.1), named .esm.min.js beside the script-tag file. The default export is the framework instance, and the same wildflower global is registered, so a page behaves the same whichever file it loads. Use it from a <script type="module">, from a bundler, or through the package's import condition:
<script type="module">
import wildflower from 'https://cdn.jsdelivr.net/npm/wildflowerjs@1/dist/wildflower.esm.min.js';
wildflower.component('counter', { state: { count: 0 }, increment() { this.count++ } });
</script>
// With a bundler or an import map: the package resolves the ES module for `import`
import wildflower from 'wildflowerjs'; // core tier
import wildflower from 'wildflowerjs/full'; // any tier: nano, mini-pool, mini, lite, spa, full
The named exports match the script-tag globals: wildflower, WildflowerJS, and on the SPA and Full tiers RouteManager (Full adds SSRManager). A .esm.dev.js twin carries the development diagnostics.
Your First Component
Create a simple interactive component in 2 steps:
Step 1: HTML Structure
<div data-component="counter">
<h2>Counter Example</h2>
<p>Count: <span data-bind="count">0</span></p>
<button data-action="increment">+</button>
<button data-action="decrement">-</button>
<button data-action="reset">Reset</button>
</div>
Step 2: Component Definition
// The global 'wildflower' instance is created automatically
wildflower.component('counter', {
state: {
count: 0
},
increment() {
this.count++
},
decrement() {
this.count--
},
reset() {
this.count = 0
}
})
That's it! WildflowerJS auto-initializes and your counter is immediately reactive.
wildflower instance automatically.
You only need to create your own instance if you disable auto-init with data-auto-init="false".
Troubleshooting Common Issues
Component Not Initializing
If your component isn't initializing, make sure:
- The component name in
data-component="name"matcheswildflower.component('name', ...) - Your component definition script runs after WildflowerJS loads
- For dynamically added components, call
wildflower.scan()
<!-- Framework loads and creates global 'wildflower' -->
<script src="wildflower.min.js"></script>
<!-- Then register your components -->
<script>
wildflower.component('my-component', {
state: { message: 'Hello' }
})
</script>
Enable Debug Mode
Enable debug mode during development for helpful console output:
<!-- Option 1: Script tag attribute (recommended) -->
<script src="wildflower.min.js" data-debug="true"></script>
<!-- Option 2: Use development build -->
<script src="wildflower.dev.js"></script>
This provides detailed logging for component initialization, state changes, and binding updates.
Flash of Empty Lists or Content
If you see lists or bound content briefly appear empty before populating, the issue is script placement. Browsers progressively render HTML as it loads, so if your component HTML appears before the framework scripts, it will briefly render unpopulated.
Solution: Place framework scripts at the start of <body>, before your component HTML:
<body>
<!-- 1. Framework scripts FIRST -->
<script src="wildflower.min.js"></script>
<!-- 2. Component HTML AFTER scripts -->
<div data-component="my-app">
<div data-list="items">
<template>...</template>
</div>
</div>
<!-- 3. Component definitions at the end -->
<script src="app.js"></script>
</body>
Script tags are blocking. The browser waits for them to load and execute before continuing to parse the HTML. By placing framework scripts first, the framework is ready before the browser even sees your component HTML.
The alternative: defer plus data-cloak. A blocking script costs you parse time, so Performance Optimization recommends <script defer> in <head> instead, which downloads in parallel and does not block rendering. The trade is that a deferred script runs only after parsing finishes, so your component HTML renders before the framework can populate it. Do not add defer to the example above: it removes the very thing that prevents the flash. Take the other route whole instead. Mark the affected elements data-cloak and add [data-cloak] { display: none !important; } to your stylesheet, which hides them until initialization removes the attribute. The !important matters, since [data-cloak] has the same specificity as a single class and any component rule setting display would otherwise win. One more condition on that route: defer belongs on external scripts only. If your component definitions are inline in the page, keep the framework tag blocking, as in the example above, because browsers ignore defer on inline scripts and the registrations would run before the framework exists.