Neither Web Components Nor Frameworks: Introducing Jails
One of the most frustrating things in today’s tech landscape is how often we find ourselves forced to choose between extremes when trying to do things differently.
We’re always living on the edge…

No matter how much you enjoy a specific framework, it’s becoming painfully clear that building something simple on the web is increasingly difficult. Across blogs and dev articles, one theme keeps coming up: modern frontend development feels unnecessarily complex. Worse still, many apps are aging poorly and becoming harder to maintain.
What’s Wrong With Today’s Frameworks?
First off, only about three frameworks dominate the ecosystem. And all of them have grown more and more complex over time — trying to cover every edge case of web development. Ironically, they often end up solving the very problems they created in the first place.
They were built on the premise that more JavaScript in the client is better. They gradually stopped focusing on solving specific problems and instead tried to solve every problem, sometimes across multiple platforms. Not just the browser anymore — now they want to own your server too. You’re no longer just adopting a framework. You’re buying into an entire ecosystem, top to bottom.
The frontend community isn’t always helpful either. To justify the complexity, framework enthusiasts often attack other tools or languages — sometimes unfairly — in a kind of survival-of-the-fittest mentality.
Have you ever heard someone bash PHP or Ruby without really understanding them?
Web Components — The Answer?
For those who developed web applications before the age of frameworks, Web Components feel like an obvious solution. They bring a strong dose of nostalgia — back when the web felt simpler, lighter, and more maintainable. The idea of returning to simplicity is extremely tempting.
But here’s the catch: Web Components represent the opposite extreme in the frontend spectrum.
In my view, Web Components are a set of low-level APIs designed to enable encapsulation, distribution, and integration of behavior — not to replace application-level architecture. They’re tools provided by the browser to help build frameworks and libraries — not to become one.
Equating Web Components with frameworks just doesn’t make sense to me. The capabilities they provide don’t even scratch the surface of the real-world challenges developers face when building even moderately complex applications in vanilla JavaScript.
And I say this with confidence — because I’ve spent most of my life building applications without frameworks.
The Real Challenges of Vanilla Development
No matter how creative your product designer is, the reality of frontend development remains the same across most applications — predictable and repetitive.

- The app reacts to user input (clicks, typing, gestures).
- It reads data from the UI, processes it, and triggers external actions (API calls).
- It receives responses, stores data locally or globally, and notifies parts of the system that the data is ready.
- It updates the UI with the new state.
Everything else?
Toggles, animations, slides, loaders — just UI fluff.
So What Makes Vanilla So Challenging?
If you decide to build something in plain JavaScript — or even with Web Components — you’ll quickly run into a familiar set of problems:
Smart Event Handling
You need an intelligent system to handle user events. Once the DOM changes, new elements might appear — and those new elements also need event listeners. Suddenly, you’re managing event lifecycles manually. This leads you to reinvent event delegation. You’ll build your own version of a pattern you thought you wouldn’t need.
Functional Grouping & Reusability
You’ll want to group related behavior and UI together in a way that’s reusable, consistent, and easy to reason about. That’s when you’ll start thinking in components — and begin designing your own abstraction layer to handle state, behavior, and DOM access together.
State Change Propagation
When your app receives data from an external source, that change might need to be reflected in multiple places. That means you’ll need some way to broadcast changes across components. You’ll probably build your own version of an Observer pattern or a Pub/Sub system — along with some naming conventions to manage your growing list of events.
View Updating & DOM Syncing
Simply using innerHTML to update the view won’t cut it. Even with the help of template literals to avoid manually updating each DOM node, you’ll soon notice your app becomes sluggish. You lose focus on input fields. Internal state gets wiped. Links behave inconsistently. Your UI starts to feel broken and fragile.
Déjà Vu All Over Again
You’ll notice that these same problems show up across projects. So naturally, you start reusing the solutions you built — abstracting them, refining them, organizing them.
Before you know it…
Congratulations, you’ve built your own JavaScript framework.
And none of those essential solutions came from Web Components. They came from you — your effort, your creativity, and your technical intuition.
Keep Evolving, But Stay Sane
You keep improving. You keep updating.
You add the features that actually make sense.
You drop the ones that only add noise.
You’re guided by your own vision — by what feels right. You borrow the good ideas from existing frameworks — the ones that stood the test of time. At the same time, you invent your own patterns — leaner, simpler ways to solve real-world problems without the usual bloat.
You’re not chasing hype.
You’re just building — honestly, intentionally, sustainably.
And then…
You do this for about 10 years.
Iterating. Refactoring. Rethinking everything.
Not because you wanted to build a library… but because you needed one that didn’t exist yet.
And suddenly —
You realize you’ve built something different. Something that might be worth sharing.
Not a bloated framework.
Not a reinvention of the web.
Just a lightweight, modern, and deeply practical tool — born from actual experience.
Enter Jails

Jails is my answer to the false choice we’ve been forced to make for years in frontend development:
- Either you build everything manually — and risk not only solving your app’s problems but also fighting the ones you created with your own DIY architecture.
- Or you choose a full-blown framework — and get buried in layers of abstractions, indirections, and conventions just to solve something that should’ve been simple.
Why does it have to be one or the other?
Why can’t we have a tool that’s powerful enough to help — but lightweight enough to stay out of the way?
That’s exactly what Jails aims to be:
A pragmatic middle ground.
A minimal layer that helps you organize, scale, and reuse code — without hijacking your project or dictating how you should think.
How Jails Stands Apart From Traditional Frameworks
Jails is designed specifically to solve the challenges of client-side web development — without imposing unnecessary complexity or locking you into a full-stack ecosystem.
Backend Agnostic by Design
Jails works seamlessly with any backend — not just Node.js. Whether your server is powered by Ruby, PHP, Go, Java, Python, .NET, or anything else, Jails plays well with it.
It avoids the pitfalls of isomorphic (or “universal”) JavaScript — no guessing games about whether a line of code runs on the server or in the browser.
No hydration mismatches. No SSR headaches. Just clean separation of concerns.
Built for Progressive Enhancement
Jails adapts to your rendering strategy:
- It works great with static HTML or server-side rendered content, enhancing it dynamically on the client.
- Or, it can be used to build fully dynamic client-side interfaces — it’s your choice.
Whether you’re rendering HTML on the server or creating it on the fly, Jails integrates naturally and keeps things fast and accessible.
Minimal by Nature, Vanilla at Heart
Jails stays close to the vanilla development model, adding just a small layer of features that help:
- Lower the learning curve
- Reduce boilerplate
- Solve common problems directly and clearly
It’s not about reinventing the web — it’s about making the real web easier to use.
Embraces the JavaScript Ecosystem — Doesn’t Replace It
Jails doesn’t create its own ecosystem.
No custom router. No proprietary state system. No framework lock-in.
Instead, it lets you integrate with any JavaScript library you already use and love. Whether it’s for routing, state management, or UI components — Jails fits in without taking over.
How Jails Solves the Pain of Vanilla Development
Functional Grouping & True Reusability
Jails brings structure to your code — without imposing a rigid architecture.
It offers a clean, organized way to group logic and behavior around HTML elements, making your components self-contained and reusable.
It’s like having a tiny toolkit built specifically to make vanilla development actually enjoyable — and scalable.
<app-example></app-example>
import { type Component } from 'jails-js'
export default function appExample ({ main, elm } : Component) {
//Variables on top
const somevariable = 42
// Constructor phase in the middle
main(() => {
log('Hello World, this is me!')
})
// Functionalities and details on bottom
const log = (message: string) => {
console.log(message, elm)
console.log('My lucky number is', somevariable)
}
}
import { register, start } from 'jails-js'
import * as appExample from './components/app-example'
register('app-example', appExample)
Smart Event Handling / View Updating & DOM Syncing
Jails provides built-in helpers that handle two of the hardest problems in vanilla JavaScript development — event delegation and DOM syncing — in a way that’s both powerful and intuitive.
<app-text-typing class="text-center">
<h1>Text Typing</h1>
<input type="text" class="form-control" html-static />
<div for="">
<input type="text" name="" class="form-control" html-static />
</div>
<p>
Hello this is what I'm typing: <span inner="">Nothing yet...</span>
</p>
<button>Add Another Input Field</button>
</app-text-typing>
import { type Component } from 'jails-js'
export default function appTextTyping({ main, on, state }: Component) {
main((_) => {
on('input', 'input[type="text"]', onInput)
on('click', 'button', addAnotherField)
})
const onInput = (e) => {
state.set({ myTextVar: e.target.value })
}
const addAnotherField = (e) => {
state.set((s) => s.list.push({ name: 'field-' + Math.random() }))
}
}
export const model = {
myTextVar: '',
list: [],
}
import { register, start } from 'jails-js'
import * as appTextTyping from './components/app-text-typing'
register('app-text-typing', appTextTyping)
State Change Propagation
One way to communicate between descendant components is through custom DOM events using the emit helper — or by broadcasting messages to all components in the system that are listening for a specific event.
Component A:
import { type Component } from 'jails-js'
export default function ComponentA ({ main, publish } : Component) {
main(async () => {
const users = await fetchUsers()
publish('users:ready', users) // Broadcast that users are ready!
})
const fetchUsers = () => {
return fetch('https://jsonplaceholder.typicode.com/users')
.then(response => response.json())
}
}
Component B:
import { type Component } from 'jails-js'
export default function ComponentB ({ main, state, subscribe } : Component) {
main(() => {
subscribe('users:ready', listUsers)
})
const listUsers = (users) => {
state.set({ users })
}
}
export const model = {
users: []
}
<component-b>
<h2>Users</h2>
<ul for="">
<li>
<p inner=""></p>
</li>
</ul>
</component-b>
Still Not Convinced?
Alright then — try rebuilding the features mentioned above using only native Web Components.
How much extra code will you have to write?
How many low-level problems will you need to solve yourself?
Sure, you can ditch frameworks and use Web Components in production.
But the real question is:
Should you?
Conclusion
When it comes to Jails, I’ve only scratched the surface.
Not because it’s packed with a million features — but because it solves a wide range of real-world problems in surprisingly simple and elegant ways, making it hard to showcase everything in a single post.
Every library or framework exists to solve problems.
What sets them apart is their philosophy — how they approach those problems, which trade-offs they embrace, and what they consider worth solving in the first place.
My philosophy is different.
Jails stays very close to vanilla development, but adds just the right amount of power to solve everyday challenges in the smartest, most optimized, and most sophisticated way possible — without losing clarity or control.
If you’ve made it this far and something here resonated with you, I’d love to invite you to explore Jails a bit more. I think you’ll be pleasantly surprised.
🔍 Here’s a collection of examples:
https://stackblitz.com/@Javiani/collections/jails-organization
And a demo of a classic — yet non-trivial — problem: a full Todo MVC app
https://medium.com/media/117e2c6c8fc5aa068cb463ad4cc6a720/hrefSee you next time — and until then, happy coding! 🚀
Neither Web Components Nor Frameworks: Introducing Jails was originally published in Jails-org on Medium, where people are continuing the conversation by highlighting and responding to this story.