Modern web development runs on a tax system. Every framework abstraction is a recurring bill: dependencies to maintain, toolchains to learn, performance hits to absorb. Calling these "trade-offs" is too polite. They are taxes, and the most productive thing you can do is stop paying. Build equity instead.
The chart above shows benchmarks from a Lorenz attractor demo, using just DOM entities, and how different frameworks handle increasing entity counts.
The Lorenz attractor is a good benchmark because we can use identical math across frameworks to test how their reactive systems can keep up with high-throughput DOM display.
The raw numbers are not as important as the curve shapes. In other frameworks, their logic starts interfering early, while with WildflowerJS, the logic isn't the bottleneck, DOM-rendering is, so you see a smooth curve over the course of the graph. Other frameworks fall off the cliff.
You can see how, when using just the DOM, performance will start to degrade as the browser's handling of the throughput becomes the bottleneck. But you can also see from the chart how platforms react and manage even moderate loads differently, depending on their reactivity flow.
Tax 1: High-throughput reactivity tax
This is just one of the taxes that you pay as a reactive developer: reactive systems are not built for high-throughput DOM interactions.
WildflowerJS offers data-pool for high-throughput DOM. A second reactive path: pull-based reactivity for games, simulations, particle effects, and for high-item-count lists.
<div data-component="dust">
<div data-pool="particles" data-key="id">
<template>
<div class="particle"
data-bind-style="{ left: x + 'px', top: y + 'px' }"></div>
</template>
</div>
</div>
<script>
wildflower.component('dust', {
pools: { particles: {} },
init() {
const batch = [];
for (let i = 0; i < 1000; i++) {
batch.push({ id: i, x: Math.random() * 800, y: Math.random() * 600 });
}
this.pools.particles.add(batch);
},
tick(dt) {
for (const p of this.pools.particles.items) {
p.x = (p.x + dt * 0.05) % 800;
}
}
});
</script>
One template, 1,000 entities, no per-item subscriptions, mutating in place inside tick. The dt argument is the framework-supplied frame delta, so motion stays smooth at any refresh rate without you having to track time yourself.
Tax 2: Multiple models tax
React has hooks, context, reducers, suspense, server components. Vue has Options API, Composition API, Pinia stores, composables. Each is a different mental model for "state that reacts to other state," with different rules about when it runs, what it can close over, and how it tears down. You can't transfer intuition cleanly from one to another, and half of senior code review becomes "should this be a hook or a context, a store or a composable." Those questions exist because the framework offers multiple primitives that do similar work.
The cost shows up before you've written a line. You open a file and pick which reactivity flavor to reach for, before you've modeled the domain.
WildflowerJS has one entity model. Components, stores, plugins, and pools share the same shape: state, computed, methods, lifecycle. The decision moves from "which primitive" to "which entity": a domain question, not a framework question.
A concrete example: the internal project tracker built as v1.1's canary app has five entity types: Members, Teams, Cycles, Issues, and the relations between them. Five entities, same shape five times. Reading the code, the cognitive load is "what's a Cycle and how does it relate to an Issue," not "is this a hook or a context."
The framework gets out of the way of the modeling. After a few hours, you stop thinking about reactivity primitives and just think about the domain. New components, new stores, new pools, all the same shape and lifecycle as the last one. The framework stops competing with the domain for attention, and the work feels lighter, almost obvious.
The unification extends to performance. When you reach for high-throughput rendering, pool entities use the same shape as everything else. There's no second mental model for the parts of your app that go fast.
→ Try the Project Manager demo
→ See our Entity Model documentation
Tax 3: Tooling tax
Modern frameworks require you to be as fluent in the tooling as you are in the framework.
They ship with a toolchain: Vite, webpack, esbuild, dozens of plugins. Your team has to learn it, your CI has to build it.
These tools are required because the frameworks make you write in non-standard syntax, non-standard file formats, because the frameworks themselves require optimizations through compilation to be competitive. And this tooling becomes part and parcel to your knowledge of the framework.
WildflowerJS has no build step. Drop in a script tag and code. Your IDE understands it, your CI has nothing to configure, you can start immediately. And if you think a framework needs a build step to be competitive in benchmarks, the truth is other frameworks need the build step to be competitive. WildflowerJS does not.
Tax 4: Supply chain safety tax
A counter app in Next.js, with the defaults most developers accept (TypeScript + Tailwind + ESLint), installs 429 packages and fills 457 MB of node_modules before you've typed a line of logic. A Vite + React counter with the same real-world layer: over 200 packages.* Each one is an attack vector. Each maintainer holds a key to your build.
See event-stream (2018), UAParser.js (2021), node-ipc (2022), Shai-Hulud (2025), and most recently Mini Shai-Hulud (May 2026) - the same worm class evolved with a new trick. In a single 6-minute publishing window on May 11, 2026, Mini Shai-Hulud compromised 170+ packages: all 42 @tanstack/* packages, Mistral AI's entire SDK, UiPath's 65-package automation suite, OpenSearch (1.3 million weekly downloads). OpenAI was among the named victims. The malicious versions carried valid SLSA Build Level 3 provenance attestations - the first documented npm worm whose payloads were cryptographically vouched for by GitHub Actions itself. The "safe to trust" signal didn't help, because the signal came from infrastructure the worm had already compromised.
One unpaid maintainer, millions of weekly downloads, one compromised credential away from shipping attacker code into your users' browsers. Your framework's supply chain becomes your supply chain. Now in 2026, AI-driven vulnerability probing allows automated threats to find every crack in your 400MB of node_modules before you've even deployed. What's coming next?
WildflowerJS is one file. Drop it in a <script> tag and code. No toolchain. No dependencies. With v1.1, the framework dist files themselves are built with no npm transitive dependency attack surface. No strangers' code between you and your users. Security-conscious teams can audit it in an afternoon and move on.
Tax 5: Syntax tax
.vue, .svelte, .jsx. Not HTML, JavaScript, or CSS. Every one requires a compiler. Your IDE needs a plugin to understand them. Your linter needs a custom parser. And AI assistants have to be trained on your framework's dialect and build steps to help you write efficient, optimized code.
WildflowerJS apps are written with 100% standard HTML, JS, and CSS. Tools that know the standards can understand WildflowerJS apps, and developers who know JavaScript can read your code without a translation codex. Your IDEs never need a codex for your WildflowerJS code. Version 1.1 brings a DevTool panel for convenience, but you don't need it to be productive.
Tax 6: Escape-from-platform tax
Or, the "Ecosystem tax."
When the framework has a virtual DOM, it no longer plays nicely with DOM-mutating libraries. Every framework eventually seems to require its own version of every standard browser function. Forms become RHF. Fetch becomes React Query. Validation becomes Zod. Animation becomes Framer Motion. For those features that you need a vanilla JS library for, you now need a wrapper. Then the frameworks and their adherents turn around and tout the size of their ecosystem vs other frameworks.
WildflowerJS does not have a virtual DOM. this.$el('.container').el hands you a real DOM element. Every vanilla JS library works out of the box, without a wrapper, plugin, or workaround. Your ecosystem is the entire web platform.
→ See our Third-Party Library Integration documentation
Tax 7: Memoization tax
React doesn't know which parts of your component depend on which state, so it re-runs the whole component on every state change. The fix is manual caching: useMemo, useCallback, React.memo, and the dependency-array discipline that comes with each. Forget a dependency and you ship a stale closure. Add the wrong one and you cache nothing.
Vue and Svelte's compilers do most of this work for you, but not all of it. You still have to know when to reach for v-memo in Vue or how Svelte 5's runes track what changed, or things slow down.
WildflowerJS doesn't ask, and doesn't demand. Each binding tracks its own state. When count changes, only the elements bound to count update. No memoization. No dependency arrays. No rituals.
→ See our Reactivity documentation
<div data-component="counter">
<button data-action="decrement">-</button>
<span data-bind="count"></span>
<button data-action="increment">+</button>
</div>
<script>
wildflower.component('counter', {
state: { count: 0 },
increment() { this.count++; },
decrement() { this.count--; }
});
</script>
The <span> is the only DOM the framework touches when count changes. There is no useMemo, React.memo, or dependency array. The framework already knows what depends on what.
Tax 8: Black-box debugging tax
After the build step, the code running in your browser is not the code you wrote. DevTools show you a compiled artifact. Source maps help, but they hide closure transforms, hoisting, and inlining. When something breaks in production, you're debugging code you've never seen.
With WildflowerJS, the code you wrote is the code that runs. View-source shows it. Your call stack in DevTools is your call stack, with your function names. There is no compiled artifact between you and the runtime.
Tax 9: Troubleshooting tax
When you make a mistake, where does the framework point? In React, a stale closure or a wrong dependency array surfaces three renders later as a hook-ordering error or a long stack trace into reconciler internals. The fix recipe lives on Stack Overflow, not in your console. Vue and Svelte are better, but their compiler warnings often point at generated output rather than the source you wrote.
WildflowerJS treats every cryptic error as a framework bug. Stable [WF-NNN] warning codes name the mistake, point at your code, and link to a fix recipe at a stable URL. When the framework can detect a userland shape problem (an object returned from a class binding, data-model pointed at a read-only store path, a computed function that threw) the warning fires at the moment of the mistake, with the right next move spelled out:
[WF WF-505] Class binding shape mismatch (coerced): computed returned an object; coercing truthy keys to a class string
↳ Suggestion: A computed should return a string. For inline expressions, write `data-bind-class="{'is-active': cond}"`.
↳ Docs: https://www.wildflowerjs.com/docs/error-codes?code=WF-505
The principle: the troubleshooting surface is your code, not framework internals. When you're wrong, the framework's job is to teach you immediately, not to leave you reading reconciler stack traces.
Tax 10: Hydration tax
SSR frameworks render HTML on the server, ship the entire component tree to the browser, and re-render it just to attach event listeners. You pay for the same render twice: once on the server, once in the client.
WildflowerJS doesn't re-render. It walks the HTML the server already sent, reads the attributes, and attaches reactivity to the existing nodes. No second pass. No hydration mismatch errors. No content flash.
Tax 11: Knowledge depreciation tax
In other frameworks, you don't own your skills, you rent them. And every few years a rug-pulling shift ups the rent.
Class components, then hooks, then server components, then whatever comes next. Each shift devalues what you know. The framework you mastered in 2019 is obsolete in 2026.
With WildflowerJS, you're investing in core web standards, so your investment keeps compounding. You are building equity, not paying rent.
Tax 12: Form-handling tax
React's controlled-input model re-renders on every keystroke. The fix is React Hook Form (~45k stars on GitHub), which exists solely to undo that. The framework's reactive primitive is so wrong for forms that the most popular form library in the ecosystem is one that turns the framework's reactivity off during typing.
WildflowerJS uses data-model plus the browser's native Constraint Validation API (required, pattern, :invalid). Forms are HTML again. The skill is "I know HTML forms," and it transfers to every browser-rendered app you'll ever build.
<form data-component="signup" data-action="register">
<input data-model="email" type="email" required>
<input data-model="password" type="password" minlength="8" required>
<button>Sign up</button>
</form>
<script>
wildflower.component('signup', {
state: { email: '', password: '' },
register(event) {
event.preventDefault();
if (!event.target.checkValidity()) return;
fetch('/api/signup', { method: 'POST', body: JSON.stringify(this.state) });
}
});
</script>
data-model does two-way binding directly. required, type="email", and minlength are the browser's job. There is no form library and no schema.
→ See our Basic Form Handling documentation
→ See our Advanced Forms documentation
Tax 13: Data-fetching tax
useEffect-based fetching is broken in roughly the same way in every reactive framework. So every ecosystem grew its own fix. React has TanStack Query and SWR; Vue, Svelte, and Solid have their own respective adapters.
Even the libraries that try to be cross-framework, TanStack Query in particular, share their core but still ship a separate adapter per framework, because the framework's reactivity primitives don't compose across the boundary. The concepts (cache keys, stale times, mutations) transfer, but the framework binding is effectively something you re-learn in every ecosystem.
In WildflowerJS, the store IS the cache. Stores are reactive containers, so the data you fetch into them is already wired to every subscribed component. The fetch lives in the store. Invalidation lives in the store. Stale time, retries, refetch on focus are normal store methods on the same object as the data. There is no adapter package, resolver, or separate cache that has to be told when your reactive state changed.
<div data-component="user-list">
<p data-show="$users.loading">Loading...</p>
<ul data-list="$users.items" data-key="id">
<template><li data-bind="name"></li></template>
</ul>
</div>
<script>
wildflower.store('users', {
state: { items: [], loading: false },
async load() {
this.loading = true;
this.items = await fetch('/api/users').then(r => r.json());
this.loading = false;
}
});
wildflower.component('user-list', {
subscribe: ['users'],
init() { this.stores.users.load(); }
});
</script>
The store is the cache. State changes inside load() propagate to the subscribed component automatically. There is no second reactive system to bridge into and no per-framework adapter package.
→ See our Basic Stores documentation
→ Physarum Store Demo
Tax 14: Schema-duplication tax
Pick a typed entity in a modern React or Vue app, a User or an Order or anything that flows from your database to your UI. The same shape gets declared three times before it reaches your business logic. Once as a TypeScript interface (the IDE needs it). Once as a Zod schema (the runtime needs it because TypeScript erased the IDE's interface). Once in the ORM or API layer (Prisma, tRPC, GraphQL codegen each ship their own). When the shape changes, you change it in three places, or one of them starts lying.
The multiplication isn't TypeScript's fault. It comes from the recommended-path companions every modern framework points you toward. tRPC ties Zod to your component tree. React Hook Form's most-documented resolver is Zod. Prisma generates a parallel type tree from its schema file. None of these are wrong tools, but each demands its own copy of the shape, and popular stack starters pull them all in.
WildflowerJS doesn't prescribe a data-layer companion. There is no opinionated validator, end-to-end type pipe, or codegen step. You fetch where you need to fetch, you validate where untyped data crosses the boundary, and the validated value lands in a store as plain reactive state. One declaration, in the place every web app since 1996 has put it: at the boundary. TypeScript is supported (full type definitions, generic components, typed stores), but it's an option, not the on-ramp to three parallel schemas. TypeScript types your methods, computeds, and stores. The dev validator types your bindings. Two halves of one safety net, not a missing one.
→ See our TypeScript documentation
Stop renting and start building equity
Modern reactive frameworks ask you to pay more than a dozen taxes before you write an application. We haven't even covered the bundle tax, the onboarding tax, or the testing tax. For all of those, WildflowerJS asks you to use a <script> tag. The skills you build are as "close to the metal" as you can get while maintaining performance and robustness.
None of these taxes improve your work or your users' experience. They hook you, then lock you in with debt that's easier to keep paying than to escape. You're on a treadmill, running to stay ahead of the problems the tools themselves invented.
When you write code with WildflowerJS, you aren't writing code for a framework. You are writing code for the Web. Your logic is transparent, your output is standard, and your assets are auditably secure.
WildflowerJS reaches new developers exactly one way: someone who tried it tells someone else. If you checked out WildflowerJS and resonated with its mission, please pass on a link to friends. Thanks!
* As of April 2026, Next.js 16.2 and Vite 5.x.