They took the render loop. Take it back. That's a coup.
Somewhere along the way, we stopped building websites and started configuring them.
Adding an interactive button now requires a bundler, a transpiler, a framework,
a state management library, and 200MB of node_modules heavier than the
application they exist to serve. The build pipeline takes longer to set up than
the feature it enables.
And for what? So we can chain custom hooks five levels deep —
useAuth calls useUser calls useQuery calls
useMemo calls useCallback — and when it breaks, the stack trace reads like
a riddle. State got so complicated we invented atoms.
Atoms! We took the simplest thing in programming — an object — rebranded it
as a primitive particle, and asked developers to wire them together like physics experiments.
It's an object. It was always just an object.
Ask a React developer what happens when they call setState. Which components
re-render? In what order? Why did that child re-render when its props didn't change?
They probably don't know. They have never known. The machine was designed to be invisible —
until it breaks, and then you're debugging a 15-frame stack trace through a reconciler
you were never meant to understand.
The central lie of modern frontend is: "you cannot be trusted with the render loop."
So the framework took away your control, then gave you back a worse version of it
and called it an optimization API.
useMemo to stop computing things you shouldn't be computing.
useCallback to stabilize references that are only unstable because
React tears down your function on every render.
Every performance issue you've ever debugged was a when problem —
and these tools are just telling the framework when not to render,
which is just telling it when to render with extra steps.
Frontend development is hard. React didn't simplify it — it moved the complexity behind abstractions that break in new ways. Now "senior frontend engineer" means navigating framework internals instead of building interfaces. The browser has had a native component model since 2020. The platform caught up years ago. The industry didn't notice because it was too busy configuring Webpack.
Drop a script tag into any page. Add components where you need them. No rewrite. No migration. No build step.
import { CoupElement, html } from 'coup'
class MyCounter extends CoupElement {
static tag = 'my-counter'
state = { count: 0 }
template() {
return html`
<button @click=${() => { this.state.count++; this.render() }}>
Clicked ${this.state.count} times
</button>
`
}
}
MyCounter.define()
That's a real component. You own every render. That's the coup —
taking back the render loop. Efficient DOM updates, automatic cleanup.
It works in a static HTML page, a Rails app, a WordPress theme, a marketing site —
anywhere you can put a <script> tag.
Need a search box with autocomplete? A form with validation? A live-updating dashboard? Drop in one component. The rest of your page doesn't know or care that coup exists.
Need a full app? Keep going. Parent-child props, shared stores, event-driven communication between components — coup scales from a single widget to an full SPA with routing. Same tool, same patterns, no second framework when things get "serious."
Coup doesn't auto-render when you change state. You call this.render().
Every other framework hides this — signals, proxies, dirty-checking, compiler magic.
Coup doesn't. You mutate your state, you tell the DOM to update.
They took the render loop away. You're taking it back.
When the UI is wrong, the bug is obvious: you changed state but forgot to call
this.render(). Turn on CoupElement.debug = true and it warns you.
When a framework auto-renders, the failure mode is
invisible over-rendering — components re-rendering 30 times a second
because a function reference changed. Good luck finding that.
No build step. An import map and a script tag. Open the HTML file. It works.
No invisible machinery. When you call this.render(), lit-html
patches only the expressions that changed. Step through it in a debugger in under a minute.
Five stack frames, not fifty.
No invented problems. No stale closures — your methods are methods. No dependency arrays — lifecycle hooks fire when their names say they fire. No rules of hooks — there are no hooks. State is a property. A handler is a method.
One file. That's the entire framework — not just the "core," not the "runtime," not the part before the plugins load. The whole thing. One dependency (lit-html, ~3KB). You can read it during a coffee break and know exactly what happens between your state change and the DOM update. Custom elements — the browser's own component system, shipped since 2020, ignored by an industry addicted to compilation.
We added reactive state to coup — set a property, the component re-renders automatically. Elegant. Then we built a Spotify playlist manager with it and found the bugs: SDK callbacks setting properties with equal-but-not-identical objects, each one quietly triggering a render. 169 renders per play click — and the response was to start writing to the DOM directly, bypassing the framework to avoid the cost. Invisible over-rendering — exactly what coup was built to prevent.
So we reverted. Went back to this.render(). Added a two-line guard — if state
didn't actually change, don't render. 169 renders → 1.
That guard was trivial to write because this.render() is a line you choose to add.
Under reactive state, preventing a render means understanding the
framework's equality semantics. A much harder problem.
Five hooks. No dependency arrays. No cleanup-return-function guessing games.
connected() Added to the DOM — fetch data, start timers.disconnected() Removed — clean up whatever you started.firstUpdated() After first render — DOM is populated, safe to query and measure.updated() After every render — measure, scroll, init widgets.propsChanged(changes) Props changed — { name: { old, new } }.Thirteen runnable examples. No installs. No builds. Click and read the source. Every one is a single HTML file with an importmap and vanilla JavaScript.
I use coup both ways — drop-in components on existing pages and full single-page apps built entirely with it. Same tool. Same patterns. I hope you will too.
Every .html file you open without a build step is a small act of rebellion. Start here:
git clone https://github.com/sandro/coup.git
cd coup
open examples/1-hello/index.html
No install. No build. No wait. That's a coup.