WildflowerJS Reactive JS, No BS*

A no-build reactive JavaScript framework, rooted in the web platform.
No build step. No dependencies. No lock-in.

<script src="wildflower.min.js"></script> ...and start building.

Back to Basics

The code you write is 100% web standard code. HTML stays HTML. JavaScript stays JavaScript. CSS stays CSS. No JSX, no templating language, no custom syntax to learn. If you know the web platform, you already know how to use this.

WildflowerJS extends the web platform. It doesn't replace it.

Your Development Simplified

Because you develop with 100% web standards, every tool in your existing chain already understands the code: IDE, browser DevTools, linter, formatter, screen reader, SEO crawler. Nothing to install, no custom file types, no sourcemaps. Save the file, refresh, and your change is live.

Just be a web developer.

Batteries Included: One Mental Model

Router, SSR, stores, computed properties, two-way binding, event modifiers, data pools, and TypeScript types, all built in, all speaking the same language. Learn data-bind once and you know binding everywhere: lists, pools, stores, forms. There's no five-library stack to keep in sync.

One script tag. Everything you need.

<div data-component="counter">
  <span data-bind="count"></span>
  <button data-action="increment">
    +1
  </button>
</div>

<script>
wildflower.component('counter', {
  state: { count: 0 },
  increment() { this.count++ }
})
</script>

How It Works

data-bind connects state to the DOM.

data-action connects events to methods.

this.count++ triggers a precise DOM update.

Mutate state. The DOM updates.

Two Reactivity Modes

data-list for automatic reactivity: mutate state, DOM updates. data-pool for explicit control: plain objects, zero proxy overhead, you say what changed.

Same template syntax. Different performance profile. From interactive forms to per-frame particle systems. You choose the right tradeoff for the job.

Try it. Right-click, inspect this demo. Every dot is a real DOM element.

See full demo →

* Build Step

Zero Toolchain

Modern frameworks ask you to install a compiler, a bundler, a package manager, hundreds of fragile transitive dependencies, and a framework-specific file format, before you write a single line of your application.

WildflowerJS was built starting from a single principle: no build step, no tooling. Ever.

WildflowerJS asks you to add a script tag.

There's no CLI scaffolding step, no config files, no .vue/.jsx/.svelte source format. You don't debug through sourcemaps or wait on a build pipeline. Your project has zero dependencies.

Performance isn't a tradeoff. Build steps optimize bundle delivery, not the runtime work that follows it. WildflowerJS writes directly to the DOM, with no virtual DOM or reconciliation pass between state change and update, so it doesn't need a build step to be fast.

The framework is full-featured without the toolchain: router, SSR, stores, computed properties, transitions, pools. You don't need a toolchain to use any of it.

my-app/
  index.html
  app.js
  style.css
  wildflower.min.js

That's the entire project. No package.json.
No node_modules. No config files. Ship it.

Zero Install. Zero Attack Surface.

Every dependency you install is trust extended to a maintainer you've never met, running scripts on your dev machine and in your CI. A typical React + Vite + UI‑lib setup pulls in 300+ transitive packages before you write a feature.

Each one is a potential intrusion vector. NPM worms, OAuth chains compromising deploy platforms, postinstall hijacking: the supply chain is now where production code gets compromised, not the deploy. And signing isn't a backstop: Mini Shai‑Hulud (May 2026) compromised 170+ packages whose malicious versions carried valid SLSA Build Level 3 provenance, because the attestation came from build infrastructure the worm had already taken over.

WildflowerJS users don't have this attack surface, by construction. There is no npm install, no postinstall script, no transitive package graph. The framework is one file you copy or pin by hash.

As of v1.1, the same holds for building the framework itself. WildflowerJS bundles with a vendored rollup and terser pipeline pulled as three SHA‑512‑pinned tarballs: no npm install, no transitive packages, no postinstall scripts in the build path. The entire toolchain is three files you verify by hash.

Zero dependencies is the absence of a problem the rest of the industry has not properly addressed.

A typical React/Vue project:

  npm install
  ├── hundreds of packages
  ├── from hundreds of maintainers
  ├── postinstall scripts run on install
  └── tens to hundreds of MB of transitive code

WildflowerJS:

  <script src="wildflower.min.js"></script>
  └── 1 file.
      No transitive dependencies.

Zero Lock-in

WildflowerJS works with the DOM, not instead of it. There's no virtual DOM intercepting your code and no compiler rewriting your markup. The render cycle is yours.

That means Leaflet, DataTables, Chart.js, D3, Three.js, any library that touches the DOM, just works. No wrapper packages or framework-specific escape hatches required. Drop in a script tag and use it.

Because your code is standard HTML and JavaScript, you're never locked in. Your skills transfer and your code is more portable. If you outgrow the framework, your knowledge doesn't expire.

This also means your "ecosystem" is all of the world of vanilla JS. Without compromises or hacks.

<!-- Use any library directly -->
<div data-component="map-view">
  <div id="map" style="height: 400px"></div>
</div>
wildflower.component('map-view', {
  state: { lat: 51.505, lng: -0.09 },
  init() {
    // Leaflet works as-is. No wrappers.
    this._map = L.map('map')
      .setView([this.lat, this.lng], 13);
    L.tileLayer('https://{s}.tile.osm.org'
      + '/{z}/{x}/{y}.png').addTo(this._map);
  }
})

Precise Reactivity

When you write this.count++, WildflowerJS updates the single DOM node bound to count. Nothing else is touched. There's no tree diffing or reconciliation pass to figure that out.

This isn't a tradeoff. You get fine-grained updates and a simple mental model. Change a property, the bound element updates. That's the entire reactivity model.

Other frameworks ask you to learn signals, accessors, memos, effects, and subscription lifecycles to achieve what WildflowerJS does with a property assignment.

wildflower.component('dashboard', {
  state: {
    users: 1420,
    status: 'healthy'
  },
  computed: {
    summary() {
      return this.users + ' users, ' + this.status;
    }
  },
  refresh() {
    this.users = 1421;
    // Only the elements bound to 'users'
    // and 'summary' update. Everything
    // else on the page is untouched.
  }
})

One Reactivity Model. Everywhere.

Components, Stores, and Plugins all share the same reactive foundation. State, computed properties, and methods work identically no matter where they live. Learn it once, it works the same way in a UI component, a global store, or a framework plugin.

Other frameworks make you learn a different system for each layer. React components use hooks, but stores need Redux or Zustand, which are completely different APIs. Vue components use reactive data, but Pinia stores have their own patterns. Every layer is a new mental model.

In WildflowerJS, there's one model. A store is a component without a template. A plugin is an entity that extends the framework itself, adding directives, lifecycle hooks, and services. The same this.count++ triggers the same reactivity everywhere.

This unlocks patterns other frameworks can't express. A store can run headless physics simulations with tick(), feeding data into a component that renders it through a pool, all using the same reactive primitives, no glue code required.

// Component: reactive UI
wildflower.component('cart', {
  state: { items: [] },
  computed: {
    total() { return this.items.length; }
  }
})

// Store: global shared state
wildflower.store('user', {
  state: { name: '', role: 'guest' },
  computed: {
    isAdmin() { return this.role === 'admin'; }
  }
})

// Plugin: extends the framework
wildflower.plugin({
  name: 'notifications',
  state: { items: [], unreadCount: 0 },
  computed: {
    hasUnread() { return this.unreadCount > 0; }
  },
  add(msg) { this.items.push(msg); this.unreadCount++; }
})
// Access globally: wildflower.$notifications.add(...)

// Same state. Same computed. Same methods.

Data Pools

Every framework wraps collection items in reactive proxies, whether the item needs it or not. WildflowerJS gives you a choice: data-list for push reactivity (automatic), data-pool for pull reactivity (explicit control, zero proxy overhead).

Pools render plain objects with the same template syntax as lists. Mutate the object, call markDirty(), and only that item updates. Full CRUD, selection, bulk operations, all faster than the push-reactive path.

And because pools use pull-based rendering, they scale to simulations, games, particle systems, and data visualizations at native frame rate. Use cases that would choke a virtual DOM. No other framework has anything like this.

<div data-component="user-table">
  <tbody data-pool="users" data-key="id">
    <template>
      <tr>
        <td data-bind="name"></td>
        <td data-bind="status"
            data-bind-class="status === 'active'
              ? 'badge success'
              : 'badge inactive'"></td>
      </tr>
    </template>
  </tbody>
</div>
wildflower.component('user-table', {
  pools: { users: {} },

  init() {
    // Populate: plain objects, no proxies
    data.forEach(u => this.pools.users.add(u));
  },

  // Optional: add tick() and the same pool
  // renders every frame. Same template, same
  // data, different rendering frequency.
  // That's the only difference between a
  // display table and a particle system.
})

Built for AI-Assisted Development

Because WildflowerJS is standard HTML and JavaScript, AI code assistants already know how to write it. There's no custom syntax to hallucinate or compiler quirks to work around. The code an AI generates runs exactly as written, with no build step between generation and execution.

We go further. WildflowerJS ships an AI-optimized reference page with patterns, anti-patterns, and examples designed for code generation context windows. Our llms.txt file follows the llms.txt convention for machine-readable documentation.

And for structured app generation, our Universal App Manifest lets you describe an entire application as a JSON schema (components, state, computed properties, methods, templates) and have an AI generate the working code from the manifest, mediated through framework-specific idiom files.

You: "Build me a todo app with
WildflowerJS"

AI reads llms.txt or ai-assistant.html
     ↓
Generates standard HTML + JS
     ↓
<div data-component="todo-app">
  <input data-model="newItem">
  <button data-action="addItem">
    Add
  </button>
  <ul data-list="items">
    <template>
      <li data-bind="text"></li>
    </template>
  </ul>
</div>
     ↓
Open in your browser. It works, and you can read and understand the code.

Error Codes

Reference for all WF-* error codes. Click a category button below to filter.

Showing:

WF-001 Root element not found
Core

Framework initialization cannot find the specified root DOM element. Check that your mount target exists in the HTML before calling wildflower.start().

WF-101 Error initializing component
Components

A component's init() method or setup logic threw an exception. Check the browser console for the underlying error.

WF-102 Component instance not found
Components

Attempting to access a component that doesn't exist in the registry. Verify the component name matches its data-component attribute and that it has been registered.

WF-103 Component context not available
Components Context

A component's context object is missing when required. This usually indicates the component was destroyed or not fully initialized.

WF-104 Error in parent event handler
Components Actions

A parent component's event handler threw an exception when invoked by a child. Check the parent's method for errors.

WF-201 Error evaluating computed property
State

A computed property function threw an exception during evaluation. Check that all referenced state properties exist and have expected types.

WF-202 Circular dependency detected
State

Two or more computed properties reference each other, creating an infinite loop. Restructure your computed properties to break the cycle.

WF-203 Error setting state value
State

The reactive proxy's set trap encountered an error. This can happen when assigning invalid values or when the state object has been corrupted.

WF-204 Error deleting state value
State

The reactive proxy's delete trap encountered an error when removing a state property.

WF-205 Error loading state from storage
State

Failed to read persisted state from localStorage or sessionStorage. The stored data may be corrupted or the storage quota exceeded.

WF-206 Error saving state to storage
State

Failed to persist state to localStorage or sessionStorage. Check that the storage quota has not been exceeded and that the data is serializable.

WF-207 Invalid parameter for state update
State

A state update method received an argument of the wrong type. Verify you're passing the correct data type.

WF-208 Computed property does not exist
State

Attempting to access a computed property that hasn't been defined. Check the computed block in your component definition.

WF-209 Computed property must be a function
State

A computed property was defined as a value instead of a function. Computed properties must be functions that return a value.

WF-210 Invalid path segment
State Bindings

A dotted path like user.profile.name contains an invalid segment. Check for typos or undefined intermediate objects.

WF-211 Error in subscription callback
State Stores

A user-provided subscription callback threw an exception. Check the function passed to subscribe().

WF-212 Pool aggregate read inside a computed
State

A computed read pool.length or pool.size. Pool aggregates bypass reactivity, so the computed evaluates once and never re-runs when the pool changes, leaving the bound value silently stale. Mirror the count into reactive state inside a tick() instead, e.g. if (this.count !== pool.length) this.count = pool.length;, then bind that state. Dev-mode only; stripped from production builds.

WF-301 Error resolving data in context
Context Bindings

Context data resolution failed during binding evaluation. The bound property path may be invalid or the data structure has changed unexpectedly.

WF-302 Missing component instance in context
Context Components

A context operation requires a component instance but none is available. The component may have been destroyed.

WF-303 Error updating context
Context

The context update process encountered an exception. This is typically caused by invalid data or a corrupted context state.

WF-304 Error in context dependency notification
Context

Failed to notify dependent contexts of a data change. A dependent binding or computed property may have an error.

WF-401 Template not found for list
Lists

A data-list element has no <template> child to use as the item template. Add a <template> element inside your list container.

WF-402 Error rendering list
Lists

The list rendering process threw an exception. Check that the list data is a valid array and that template bindings reference valid item properties.

WF-403 Error updating list item
Lists

Updating an existing list item's bindings failed. The item data may have an unexpected structure.

WF-404 Error removing list item
Lists

Removing a list item from the DOM failed. The element may have already been removed or detached.

WF-405 Error in append optimization
Lists

The optimized append path for adding items to the end of a list encountered an error. The framework will fall back to a full re-render.

WF-406 Error in swap optimization
Lists

The optimized swap path for reordering list items encountered an error. The framework will fall back to a full re-render.

WF-407 Error in sparse update optimization
Lists

The optimized sparse update path (updating a subset of list items) encountered an error. The framework will fall back to a full re-render.

WF-501 Error evaluating binding expression
Bindings

A data-bind expression failed to evaluate. Check for typos in property names or invalid JavaScript expressions. Also emitted as a warning when store shorthand ($store.path) is used in data-model.

WF-502 Error evaluating class binding
Bindings

A data-bind-class expression failed to evaluate. Check that the expression returns a valid string or object.

WF-503 Failed to create HTML binding context
Bindings

Creating a context for a data-bind-html binding failed. Check that the binding path is valid.

WF-504 Error updating conditional context
Bindings

Updating a data-show or data-render conditional context failed. Check that the bound expression evaluates to a boolean-like value.

WF-505 Class binding shape mismatch
Bindings

A data-bind-class binding received a value that is not a string. The element-level path expects a space-separated class string. Inline expressions can use the {'class-name': condition} object form, but a computed property should return the resolved string itself. The framework coerces the value (truthy keys joined to a string, or the value stringified) so the page keeps rendering, but the underlying mismatch should be fixed in your code.

Wrong: computed: { classes() { return { 'is-active': this.active }; } }
Right: computed: { classes() { return this.active ? 'is-active' : ''; } }

WF-601 Error in action handler
Actions

A data-action handler method threw an exception. Check the method referenced in your data-action attribute.

WF-602 Error in component method
Actions Components

A component method execution failed. Check the method for runtime errors such as accessing undefined properties.

WF-603 Cannot emit: component instance not found
Actions Components

emit() was called but the component instance could not be located. Ensure the component is mounted and initialized.

WF-604 Cannot emit: component context not available
Actions Components

emit() was called but the component's context is unavailable. The component may have been destroyed.

WF-701 Route not found
Routing

Navigation attempted to access a route that hasn't been defined. Check your route configuration.

WF-702 Target route not found for alias
Routing

A route alias points to a target route that doesn't exist. Verify the alias target matches a defined route path.

WF-703 Error in route guard
Routing

A navigation guard function (beforeEnter, beforeLeave, etc.) threw an exception. Check the guard function for errors.

WF-704 Navigation queue exceeded retry limit
Routing

The navigation queue has exceeded its maximum retry attempts. This usually indicates a redirect loop in your route guards.

WF-705 Named route not found
Routing

Navigation by name references a route that doesn't exist. Check the name property in your route definitions.

WF-706 Invalid route configuration
Routing

A route definition has an invalid structure. Routes require at minimum a path property.

WF-707 Router already initialized
Routing

Attempting to initialize the router more than once. The router should only be configured and started once per application.

WF-708 No route matched for path
Routing

No route pattern matches the requested URL path. Consider adding a catch-all route (path: '*') for 404 handling.

WF-709 Error in route handler
Routing

A route's handler function threw an exception during execution.

WF-710 Error loading route component
Routing

An async/lazy-loaded route component failed to load. Check the network request and module path.

WF-711 Error in scroll behavior
Routing

The scroll restoration or positioning function threw an exception after navigation.

WF-712 Error in route lifecycle hook
Routing

A route lifecycle hook (beforeEnter, afterEnter, etc.) threw an exception.

WF-801 Error during SSR activation
SSR

Server-side rendered component activation failed. The server-rendered HTML may not match the expected component structure.

WF-802 Error during hydration
SSR

Hydrating server-rendered HTML encountered an error. Ensure the server-rendered markup matches the client component's expected structure.

WF-901 Store component name must be a string
Stores

wildflower.store() was called with a non-string first argument. The store name must be a string.

WF-902 Store component definition must be an object
Stores

wildflower.store() was called with a non-object second argument. The store definition must be a plain object.

WF-903 Error in store init hook
Stores

A store's init() lifecycle hook threw an exception. Check the store's initialization logic.

WF-904 Error creating store component
Stores

The store creation process failed. Check the store definition for structural errors.

WF-905 Error in external() accessing store
Stores Components

external() failed when accessing a store. Verify the store name and property path are correct.

WF-906 Error in store subscription callback
Stores

A store subscription callback threw an exception. Check the function passed to the store's subscribe() method.

WF-907 Failed to create default app-store
Stores

Automatic creation of the default application store failed. This is an internal initialization error.

WF-CSP-SYNTAX Cannot parse expression
CSP Bindings

The CSP-safe expression parser encountered syntax it cannot parse. Simplify your binding expression or check for syntax errors.

WF-CSP-UNSUPPORTED Expression uses unsupported syntax
CSP Bindings

The expression contains a syntax construct not supported by the CSP-safe expression parser. The parser supports literals, identifiers, member access, binary/logical/unary/conditional expressions, array expressions, and function calls. Anything else (arrow functions, template literals, object literals, destructuring, assignment, etc.) triggers this error. Simplify the expression or move the logic into a computed property or component method.

WF-CSP-SECURITY Blocked access to restricted API
CSP

CSP security policy blocked one of: (1) a blocked global identifier (window, document, eval, Function, fetch, setTimeout, and others); (2) a blocked property access (__proto__, prototype, constructor); or (3) a function call other than external(). In CSP mode, only external() is permitted as a function call in binding expressions.

WF-EFFECT Error resolving path in render effect
State Bindings

The render effect system failed to resolve a binding path during a reactive update. The bound property may not exist on the component's state.