A framework built from scratch ~2,000 LOC

The frontend framework you can actually read.

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.

Get started $npm create danio@latest
~8 kBgzipped core
0dependencies
54passing tests
MITopen source
Counter.jsx
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)
Live clicked 0 times
~8 kB gzipped, zero deps Fiber reconciler with bailouts SSR + hydration TypeScript types included
Why Danio

A whole framework, and nothing you take on faith.

Everything you reach for to build a real app — small enough to read, familiar enough to skip the manual.

Tiny & dependency-free

The whole engine — VDOM, reconciler, hooks, store, router — gzips to ~8 kB and pulls in nothing else.

Instantly familiar

Components, JSX, useState, useEffect, context, a Redux-style store. Nothing new to learn.

Actually readable

Every file is plain, commented JavaScript. Open node_modules/danio-js/src and the framework is right there.

Fast by default

Bailouts mean a setState re-renders one component, not the tree — ~200× faster updates on a 500-row bench.

Batteries included

A store with middleware, a History-API router, error boundaries, memo, and SSR — all in the box.

SSR + hydration

Render to HTML on the server for SEO and first paint, then hydrate the exact same nodes on the client.

See it in code

The same API you already reach for.

State, a store, routing, and server rendering — each is a few lines, and each is exactly what you'd expect.

TodoList.jsx
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>
  )
}

State that reads like a checklist

Hooks work exactly as you remember. useState gives a component memory; the setter schedules a precise re-render of just that component.

  • useState, useReducer, useRef — the full set.
  • useEffect & useLayoutEffect for side effects.
  • useMemo & useCallback when you need them.
store.js
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 Redux-style store, in the box

A predictable store with middleware, thunks, and a logger — plus useSelector and useDispatch bindings. No extra package to install.

  • createStore & combineReducers.
  • applyMiddleware with thunk and logger.
  • useSelector re-renders only on real change.
App.jsx
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>
  )
}

A real router, History API and all

Declarative routes, dynamic params, and client-side navigation with <Link> — the pieces you'd add anyway, already wired up.

  • Nested routes and dynamic :params.
  • useParams, useNavigate, useSearchParams.
  • <Link> with real History-API navigation.
server.js
// 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'))

Server rendering that hydrates

renderToString runs in plain Node with no DOM. On the client, hydrate reuses the exact nodes the server sent instead of rebuilding them.

  • renderToString & renderToStaticMarkup.
  • Better SEO and a faster first paint.
  • hydrate adopts markup — no flash, no rebuild.
The whole idea

Most frameworks hide the machine. Danio hands it to you.

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.

real source · src/core/reconciler.js
Read how it's built →
reconciler.js
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.
}
One import

A real toolkit, not just a renderer.

Rendering, state, routing, and server rendering all ship together — no plugin hunt, no version matrix.

Rendering

virtual DOM · fibers · keyed diffing

Hooks

useState · useEffect · useMemo · useRef

Store

createStore · middleware · useSelector

Router

Routes · Link · useParams · navigate

Context

createContext · useContext

Errors

<ErrorBoundary> · render + effect

Server

renderToString · hydrate

Types

TypeScript defs · JSX runtime

From zero to running

Up and running in three steps.

Scaffold, build your UI, and deploy the static output anywhere — no server required.

Scaffold a project

One command gives you Vite, JSX, an example app, and TypeScript types.

$ npm create danio@latest my-app $ cd my-app && npm run dev

Build your UI

Write components with JSX and hooks. Add the store and router when you need them.

function App() { return <h1>Hello</h1> }

Ship it

Build to static files and deploy to GitHub Pages, Netlify, Vercel — anywhere.

$ npm run build # -> dist/ ready to deploy
An honest comparison

Where Danio stands.

Danio isn't out to beat React at React. It trades ecosystem size for something you can hold in your head.

 DanioReactPreactSolid
Gzipped core~8 kB~45 kB~4 kB~7 kB
React-style APIYesYesYesSignals
Store + router in-boxYesSeparateSeparateSeparate
Readable sourceThe pointLargeCompactCompiler
SSR + hydrateYesYesYesYes
Runtime dependencies0000
Ecosystem & hiringSmallEnormousMediumGrowing
The honest part

Where Danio fits — and where it doesn't.

Pick the right tool. Danio wins when size, control, and understanding matter most.

Reach for Danio

When the framework should get out of your way.

  • Size-critical or embedded UIs — widgets, extensions, and pages where every kilobyte counts.
  • Controlled internal platforms — you own the stack and want no surprises in the dependency tree.
  • Learning how frameworks work — a real, complete engine you can read end to end.
Questions

Good things to ask first.

Is Danio production ready?
Danio is pre-1.0 (v0.1.0) and moves quickly. The core is covered by 54 unit tests against a real DOM plus a headless-browser pass, and the API is stable. It's a great fit for internal tools, embedded UIs, and side projects today; for large mission-critical apps, weigh the small ecosystem against React's.
Do I need a build step?
For JSX, yes — any esbuild/Vite/Babel setup works, and 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.
Can I use TypeScript?
Yes. Danio ships hand-written type definitions, so 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.
How is it different from React?
The API is intentionally React-familiar, so most components port with only an import change. The difference is underneath: Danio is ~8 kB with zero dependencies, ships its readable source, and bundles a store and router. It doesn't (yet) do Server Components, streaming SSR, Suspense, or portals.
Is it really zero-dependency?
Yes — the published package has no runtime dependencies. Vite and jsdom appear only as dev dependencies for the example app and the test suite; nothing ships to your users but Danio itself.

Start building in one command.

Scaffold a project with Vite, JSX, an example app, and TypeScript types — ready to run.

$npm create danio@latest my-app Read the docs