Danio is a fast, dependency-free framework — a virtual DOM, a fiber reconciler, hooks, a store, a router, and SSR in about 8 kB. If you know React, you already know Danio.
import { render, useState } from 'danio-js'
function Counter() {
const [n, setN] = useState(0)
return (
<button onClick={() => setN(n + 1)}>
clicked {n} times
</button>
)
}
render(<Counter />, root)
Everything you reach for to build a real app — small enough to read, familiar enough to skip the manual.
The whole engine — VDOM, reconciler, hooks, store, router — gzips to ~8 kB and pulls in nothing else.
Components, JSX, useState, useEffect, context, a Redux-style store. Nothing new to learn.
Every file is plain, commented JavaScript. Open node_modules/danio-js/src and the framework is right there.
Bailouts mean a setState re-renders one component, not the tree — ~200× faster updates on a 500-row bench.
A store with middleware, a History-API router, error boundaries, memo, and SSR — all in the box.
Render to HTML on the server for SEO and first paint, then hydrate the exact same nodes on the client.
State, a store, routing, and server rendering — each is a few lines, and each is exactly what you'd expect.
import { useState } from 'danio-js'
function TodoList() {
const [items, setItems] = useState([])
const [text, setText] = useState('')
return (
<form onSubmit={(e) => {
e.preventDefault()
setItems([...items, text])
setText('')
}}>
<input value={text}
onInput={(e) => setText(e.target.value)} />
<ul>{items.map((t) => <li>{t}</li>)}</ul>
</form>
)
}
Hooks work exactly as you remember. useState gives a component memory; the setter schedules a precise re-render of just that component.
import { createStore, applyMiddleware, thunk }
from 'danio-js'
function counter(state = { n: 0 }, action) {
switch (action.type) {
case 'inc': return { n: state.n + 1 }
default: return state
}
}
export const store = createStore(
counter,
applyMiddleware(thunk)
)
A predictable store with middleware, thunks, and a logger — plus useSelector and useDispatch bindings. No extra package to install.
thunk and logger.import { Router, Routes, Route, Link }
from 'danio-js'
function App() {
return (
<Router>
<nav>
<Link to="/">Home</Link>
<Link to="/todos">Todos</Link>
</nav>
<Routes>
<Route path="/" component={Home} />
<Route path="/todos/:id" component={Todo} />
</Routes>
</Router>
)
}
Declarative routes, dynamic params, and client-side navigation with <Link> — the pieces you'd add anyway, already wired up.
:params.// server — no DOM required
import { renderToString } from 'danio-js/server'
app.get('/', (req, res) => {
const html = renderToString(<App />)
res.send(`<div id="root">${html}</div>`)
})
// client — adopt the server markup
import { hydrate } from 'danio-js'
hydrate(<App />, document.getElementById('root'))
renderToString runs in plain Node with no DOM. On the client, hydrate reuses the exact nodes the server sent instead of rebuilding them.
This is the core of the reconciler — the bailout that makes updates fast. No build magic, no generated code. When something surprises you, you can open the file and read exactly why.
function beginWork(fiber) {
const current = fiber.alternate
if (current && !fiber.dirty && canReuseProps(fiber, current)) {
// This component's output can't have changed.
fiber.props = current.props
fiber.hooks = current.hooks
if (!fiber.childDirty) {
// Nothing below changed. Reuse the subtree whole.
fiber.child = current.child
return null
}
cloneChildFibers(current, fiber)
return fiber.child
}
// ...otherwise, render it.
}
Rendering, state, routing, and server rendering all ship together — no plugin hunt, no version matrix.
virtual DOM · fibers · keyed diffing
useState · useEffect · useMemo · useRef
createStore · middleware · useSelector
Routes · Link · useParams · navigate
createContext · useContext
<ErrorBoundary> · render + effect
renderToString · hydrate
TypeScript defs · JSX runtime
Scaffold, build your UI, and deploy the static output anywhere — no server required.
One command gives you Vite, JSX, an example app, and TypeScript types.
$ npm create danio@latest my-app
$ cd my-app && npm run devWrite components with JSX and hooks. Add the store and router when you need them.
function App() {
return <h1>Hello</h1>
}Build to static files and deploy to GitHub Pages, Netlify, Vercel — anywhere.
$ npm run build
# -> dist/ ready to deployDanio isn't out to beat React at React. It trades ecosystem size for something you can hold in your head.
| Danio | React | Preact | Solid | |
|---|---|---|---|---|
| Gzipped core | ~8 kB | ~45 kB | ~4 kB | ~7 kB |
| React-style API | Yes | Yes | Yes | Signals |
| Store + router in-box | Yes | Separate | Separate | Separate |
| Readable source | The point | Large | Compact | Compiler |
| SSR + hydrate | Yes | Yes | Yes | Yes |
| Runtime dependencies | 0 | 0 | 0 | 0 |
| Ecosystem & hiring | Small | Enormous | Medium | Growing |
Pick the right tool. Danio wins when size, control, and understanding matter most.
When the framework should get out of your way.
When you need what a big ecosystem provides.
npm create danio configures it for you. You can also skip the build entirely and use the hyperscript h() function directly from a <script type="module"> tag. See the docs for the no-build path.useState, components, the store, and the router are all typed out of the box. The scaffolder includes a jsconfig.json for editor autocomplete even in plain JavaScript projects.Scaffold a project with Vite, JSX, an example app, and TypeScript types — ready to run.