Dunky State‑Machine

Define states once.
Render it anywhere.

A state-machine for design systems and complex UIs.
Blazing fast sealed in pure states and events. The same behavior drops into any JS renderer. Inspired by XState and Zag.

$ npm i @dunky.dev/state-machine
  • 🔒

    Locked in by design

    No external prop, no callback, no handle into the machine. Its behavior is closed to the world. Consumers react to the machine from outside.

  • 🌍

    Take it anywhere

    The machine carries a universal, interactive UI of its own, clickable on any JS surface, precisely because it's a closed box.

  • ⚡️

    Blazing fast

    Design systems and complex UIs can run hundreds of live machines at once. Dunky is tuned for exactly that load. See the benchmark →

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
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
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
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' },
},
},
},
})
// ⏱️ clock: self-drives via `after`, no external loop
const clock = machine({
initial: 'running',
states: {
running: {
after: {
200: { target: 'running' },
},
},
},
})
// 🎲 compose: sync() fans each clock 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

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.