The Problem Nobody Talks About
You know what's wild? We've normalized this:
npm install
# *goes to make coffee*
# *comes back*
# *still installing*
# ~350 packages installed
# node_modules is now ~450MB
# half of them are security vulnerabilities
Meanwhile, junior devs are like "I just wanted to make a website" and we're like "Cool, first install these 47 build tools, configure TypeScript, set up webpack, oh and here's 200 pages of React docs."
This is insane.
Enter b0nes 🦴
b0nes is what happens when you ask: "What if we just... didn't?"
- Zero npm dependencies (not "minimal", not "lightweight", ZERO)
- Pure JavaScript (no TypeScript required, but use it if you want. But we leverage JSDocs)
- No build tools (unless you count Node.js)
- SSR & SSG built-in
- State management included (Redux-style, but simple)
- FSM routing for SPAs (XState vibes, zero complexity)
- Atomic design out of the box.
The Philosophy (aka The Rant)
1. Dependencies Are Liabilities
Every dependency is:
- A security risk
- A maintenance burden
- Someone else's breaking changes
- Another thing that can go wrong
b0nes runs on Node.js built-ins.
That's it. This code will run unchanged in 10 years.
2. Build Tools Are Often Unnecessary
You know what's a great build tool? Not having one.
// This is valid b0nes code
import { button } from './components/atoms/button/button.js';
const html = button({
type: 'submit',
slot: 'Click Me'
});
No transpilation. No bundling. No webpack config from hell. It just works.
3. Complexity Should Be Optional
Want to build a blog? Here's literally the entire setup:
git clone https://github.com/iggydotdev/b0nes.git
cd b0nes
npm run dev
That's it. No create-react-app, no next config, no "please update your node version to 18.4.2 exactly or everything breaks."
Show Me Code (Finally)
Components Are Just Functions
// src/components/atoms/badge/badge.js
import { processSlotTrusted } from '../../utils/processSlot.js';
import { normalizeClasses } from '../../utils/normalizeClasses.js';
export const badge = ({ slot, variant = 'default', className = '', attrs = '' }) => {
attrs = attrs ? ` ${attrs}` : '';
const classes = normalizeClasses(['badge', `badge-${variant}`, className]);
const slotContent = processSlotTrusted(slot);
return `<span class="${classes}"${attrs}>${slotContent}</span>`;
};
That's it. A component is a function that returns HTML. No JSX, no virtual DOM, no reconciliation algorithm, no hydration errors.
Pages Are Just Component Configs
// src/pages/home.js
export const components = [
{
type: 'organism',
name: 'hero',
props: {
slot: [
{ type: 'atom', name: 'text', props: { is: 'h1', slot: 'Welcome' } },
{ type: 'atom', name: 'badge', props: { slot: 'New!', variant: 'primary' } }
]
}
}
];
export const meta = {
title: 'Home',
stylesheets: ['/styles/main.css']
};
Routing Is Auto-Discovered
src/pages/
├── index.js → /
├── about/
│ └── index.js → /about
└── blog/
├── index.js → /blog
└── [slug].js → /blog/:slug
No route config. No getStaticPaths. It just works.
State Management (Because You Need It)
import { createStore } from './framework/client/store.js';
const store = createStore({
state: { count: 0 },
actions: {
increment: (state) => ({ count: state.count + 1 }),
decrement: (state) => ({ count: state.count - 1 })
},
getters: {
doubled: (state) => state.count * 2
}
});
store.dispatch('increment');
const current = store.getState();
const doubled = store.computed('doubled');
Redux-style, but you can actually understand it. No actions/reducers/dispatch/connect/mapStateToProps ceremony.
FSM Routing (The Cool Part)
For SPAs, we don't use a traditional router. We use finite state machines.
import { createFSM } from './framework/client/fsm.js';
const authFSM = createFSM({
initial: 'logged-out',
states: {
'logged-out': {
on: { LOGIN: 'logging-in' }
},
'logging-in': {
on: {
SUCCESS: 'logged-in',
FAILURE: 'logged-out'
}
},
'logged-in': {
on: { LOGOUT: 'logged-out' }
}
}
});
authFSM.send('LOGIN');
authFSM.is('logging-in'); // true
Why FSM?
- Impossible states become impossible (no "loading while logged out")
- All transitions are explicit (no mystery navigation)
- Easy to visualize and test
- Self-documenting code
For SPAs, we have a router FSM:
import { createRouterFSM, connectFSMtoDOM } from './framework/client/fsm.js';
const routes = [
{ name: 'home', url: '/', template: '<h1>Home</h1>' },
{ name: 'about', url: '/about', template: '<h1>About</h1>' }
];
const { fsm } = createRouterFSM(routes);
connectFSMtoDOM(fsm, document.getElementById('app'), routes);
// Navigate
fsm.send('GOTO_ABOUT');
Interactive Components (The "But Does It Even Work?" Part)
Yes, we have interactive components. Tabs, modals, dropdowns - all with zero dependencies.
{
type: 'molecule',
name: 'tabs',
props: {
tabs: [
{ label: 'Tab 1', content: '<p>Content 1</p>' },
{ label: 'Tab 2', content: '<p>Content 2</p>' }
]
}
}
The runtime is small and handles:
- Event delegation
- Memory management
- Progressive enhancement
- Keyboard navigation
All the stuff React Router gives you, but in smaller than 500KB.
What b0nes Is NOT
Let's be real:
❌ Not trying to replace React for complex SPAs with thousands of components
❌ Not for real-time apps (use WebSockets and your favorite framework. Still need to think about this one)
❌ Not opinionated about CSS (bring your own Tailwind/vanilla/whatever)
❌ Not production-tested at scale (it's v0.2.0, we're getting there)
What b0nes IS
✅ A complete toolkit with everything you need
✅ Zero dependencies means rock-solid stability
✅ SSG/SSR for content-heavy sites
✅ State management and FSM routing for SPAs
✅ Progressive enhancement (works without JS, better with it)
✅ For devs who want to understand their framework
The Current State of Things (Honesty Hour)
Look, I'm not gonna lie - I'm still working out some kinks with the SPA stuff. The FSM router is solid, but template compilation and asset paths are... let's say "a work in progress."
Some routes might 404. Some assets might not load. The SPA demo might crash your browser (kidding... mostly).
But here's the thing: This is version 0.2.0. Still learning. Still iterating. And unlike most frameworks, you can actually read the source code and understand what's broken.
Try It (If You're Brave)
git clone https://github.com/iggydotdev/b0nes.git
cd b0nes
npm run dev
Visit http://localhost:5000 and see a working site. Try the talk (slides) example at /examples/talk (and report bugs please 🙏).
Build a static site:
npm run build
# Outputs to public/
# Deploy anywhere - Netlify, Vercel, GitHub Pages
The Real Pitch
We've been overengineering web development for years. Junior devs are drowning in tooling. Senior devs are exhausted by constant churn.
What if we just... built websites?
No 847 dependencies. No webpack configs. No "this breaking change affects 0.01% of users but you must update immediately."
Just:
- Write components (functions that return HTML)
- Compose pages (arrays of components)
- Ship static files
Is b0nes perfect? No.
Is it production-ready for everything? Not yet.
Will it replace React? Probably not.
But it might just make web development fun again.
Links
--
🐙 GitHub
📦 npm
📖 Docs (it's the README, we're indie like that)
🐛 Issues (please report bugs!)
Final Thoughts
If you've read this far, you're either:
- Interested in zero-dependency frameworks
- Hate-reading while using your 847-package React app
- My work colleagues (yow!)
Either way, give b0nes a try. Break it. Report bugs. Contribute. Learn.
Or don't. Keep using React. It's fine. We're all just trying to ship websites here.
But if you're tired of npm install taking longer than your actual dev work... well, you know where to find b0nes.
— Iggy
Creator of b0nes, destroyer of node_modules folders
P.S. Yes, the SPA stuff is janky right now. We're working on it. PRs welcome. Please don't roast me too hard on Twitter.
P.P.S. - If you're from Vercel or Netlify and want to sponsor this, my DMs are open.
P.P.P.S. I did a presentation at DDDBrisbane 2025 and created a slides component to be used live. You can find the presso in the /examples/talk folder in the repo.
Ready to pick a b0ne?
Top comments (0)