Dunky State‑Machine

Define states once.
Render it anywhere.

A state machine built for performance
States as pure, serializable data you can plug into anything that renders. Inspired by XState and Zag.

$ npm i @dunky-dev/state-machine

Why Dunky

  • 🌎

    Truly agnostic

    No environment assumption at all — one step beyond Zag's headless idea. Compare →

  • ⚡️

    Fast at scale

    ~8× XState's throughput, flat-ish memory, surgical re-renders. See the benchmark →

  • 🧠

    Behavior, not render

    A pure behavioral kernel — states, events, context. Runs on the DOM, React Native, or any JS runtime.

Fast machines

Few bytes of state, events and context per machine. A tiny footprint can spin up thousands of them

Built for high density × high frequency.

// 🟡 pacman — eats until the ghost gets him
const pacman = machine({
  initial: 'eating',
  context: { x: 1, y: 1, dir: 'right', mouth: 'open' },
  states: {
    eating: { on: {
      step: { actions: act($ => ({ x: $.event.x, y: $.event.y })) },
      die:  { target: 'dead' },
    } },
    dead: { on: { revive: { target: 'eating' } } },
  },
})
// 👻 ghost — chases on each tick, stops on a catch
const ghost = machine({
  initial: 'roaming',
  context: { x: 11, y: 10, dir: 'up' },
  states: {
    roaming: { on: {
      tick: { actions: act($ => chase($.context, $.event)) },
      stop: { target: 'stopped' },
    } },
    stopped: { on: { reset: { target: 'roaming' } } },
  },
})
// 🍒 board — dots, cherry, score
const board = machine({
  initial: 'playing',
  context: { dots, cherry, score: 0 },
  states: {
    playing: { on: {
      eat:    { actions: act($ => scoreAt($.context, $.event)) },
      caught: { target: 'caught' },
    } },
    caught: { on: { reset: { target: 'playing' } } },
  },
})
// ⏱️ a clock machine self-drives via `after` — no external loop
const clock = machine({
  initial: 'running',
  states: { running: { after: { 200: { target: 'running' } } } },
})

// 🎲 compose the four; sync() fans each beat to all regions in order
const game = compose({ clock, pacman, ghost, board })
game.sync(() => {
  const { x, y } = step(pacman.context)
  pacman.send({ type: 'step', x, y })
  board.send({ type: 'eat', x, y })
  ghost.send({ type: 'tick', targetX: x, targetY: y })
})

game.start()

One machine.
Rendered on any JS runner.

The machine is a pure kernel with no environment assumptions. Plug it into any JS UI. If JS runs it, the machine drives it.

machine state context
pacman
ghost
board
dunky-dev.com
Web
Devices
Terminals
Ivan Banov

I spent a decade rebuilding

the same components,

over and over again.

New company, new stack, new design system — but the same button, the same tooltip, the same dialog. The behaviors never changed. All documented by the W3C. All rebuilt from scratch, every time.

Read the full story →

The stateful
machines lab.

Different domains. Same engine.
State, context, events. That's it.

Any stateful UI, is just a machine with states and transitions. The rendering layer is irrelevant.

📈 Trading panel watching
+0.00
position
🖊️ Collaborative whiteboard live

Open source

Built in the open.
Improved together.

Dunky is early and growing. If something feels off, a use case isn't covered, or you just want to say hi — every issue, PR, and share genuinely helps.

⭐ Star the repo if it helped you a tiny bit ⭐