UI runtime for
every kind of
computer

PocketJS is a portable application runtime that turns modern component code into native pixels across radically different hardware.

Modern DX solid · vue vapor · octane

Write components as you already do, in Solid, Vue Vapor or Octane. Each one compiles to the same native tree and runs on QuickJS, so the framework you pick changes your code and nothing below it.

import { createSignal, Show } from "solid-js";
import { Text, View } from "@pocketjs/framework/solid/components";

export default function App() {
  const [count, setCount] = createSignal(0);
  return (
    <View class="w-full h-full flex-col items-center gap-4 p-4 bg-slate-50">
      <Text class="text-xl text-slate-950 font-bold">Count: {count()}</Text>
      <View class="px-4 py-2 rounded-xl shadow-md bg-blue-600 focus:bg-blue-500"
        focusable onPress={() => setCount(count() + 1)}>
        <Text class="text-base text-white font-bold">Press Circle</Text>
      </View>
      <Show when={count() > 3}>
        <Text class="text-sm text-emerald-600">Reactive on real hardware.</Text>
      </Show>
    </View>
  );
}
import { ref } from "vue";
import { Text, View } from "@pocketjs/framework/vue-vapor/components";

export default function App() {
  const count = ref(0);
  return () => (
    <View class="w-full h-full flex-col items-center gap-4 p-4 bg-slate-50">
      <Text class="text-xl text-slate-950 font-bold">Count: {count.value}</Text>
      <View class="px-4 py-2 rounded-xl shadow-md bg-blue-600 focus:bg-blue-500"
        focusable onPress={() => { count.value++; }}>
        <Text class="text-base text-white font-bold">Press Circle</Text>
      </View>
      {count.value > 3 ? (
        <Text class="text-sm text-emerald-600">Reactive on real hardware.</Text>
      ) : null}
    </View>
  );
}
import { useState } from "octane";
import { Text, View } from "@pocketjs/framework/octane/components";

export default function App() {
  const [count, setCount] = useState(0);
  return (
    <View class="w-full h-full flex-col items-center gap-4 p-4 bg-slate-50">
      <Text class="text-xl text-slate-950 font-bold">{`Count: ${count}`}</Text>
      <View class="px-4 py-2 rounded-xl shadow-md bg-blue-600 focus:bg-blue-500"
        focusable onPress={() => setCount(count + 1)}>
        <Text class="text-base text-white font-bold">Press Circle</Text>
      </View>
      {count > 3 ? (
        <Text class="text-sm text-emerald-600">Reactive on real hardware.</Text>
      ) : null}
    </View>
  );
}
$ pocket create my-app
$ pocket check --target psp
ok 480x272 · text.glyphs.baked · input.buttons
$ pocket check --target vita
ok same bundle, density 2, no component edited
from your component to a pixel
pocketjs1 thread, 1 process
  1. your componentguest
  2. renderer adapterguest
  3. native treecore
  4. flexbox layout, baked style tablecore
  5. drawlistcore
  6. backend drawcore
pixels
browser or webview4 threads, 2 processes
  1. your componentmain
  2. framework runtime, vdom diffmain
  3. dom mutationmain
  4. cssom, cascade, specificitymain
  5. style recalculationmain
  6. layout, reflowmain
  7. paint recordsmain
  8. commit the layer tree across threads
  9. layer tree, tilingcompositor
  10. queue raster tasks, track invalidations
  11. rasterizationraster pool
  12. ipc to the gpu process, sync fences
  13. draw quadsgpu
  14. presentgpu
pixels
Motion compiled at build time

Animation is compiled. Keyframe timelines and spring curves are baked into the style table at build time and advanced by the Rust core on its own clock, so a page can animate with no per-frame JavaScript at all. The panel beside this runs the yui540 motion studies in WebAssembly, inside the handheld they were written for.

Loading the motion studies

Press left or right on the d-pad, or the L and R shoulders, to switch studies.

Performance quickjs guest, rust core

Your app runs as a QuickJS guest on a Rust core that draws every pixel itself. With no browser engine in the middle, the cost of a screen is close to what the bare metal can do, whether the metal is a 333 MHz handheld or a Mac.

how much machine this asks for
Memory, against a current phone
iPhone 17 Pro Max 12 GB
Sony PSP, 2004 32 MB
The same left edge, magnified 384 times
Sony PSP RAM 32 MB
A PocketJS app 8 MB
Clock, one core
A19 Pro, performance core 4.26 GHz
Sony PSP, one MIPS core 333 MHz

A whole app, drawing a smooth interface, lives in 8 MB on a single 333 MHz core. That is a quarter of what the handheld has, one part in 1536 of the phone's memory, and a core clocked 13 times slower than the one in your pocket.

Benchmarked on a Sony PSP

one MIPS core at 333 MHz · 32 MB RAM · 2004
Frame budget at 60 fps (ms)
016.67 ms budget
javascript 2.2 ms rest of cpu work, to 8.4 ms headroom

That budget holds OpenStrike: a bot-populated BSP map with a Solid JSX HUD, running at 60 fps on hardware from 2004.

Why not just ship a virtual DOM · hero demo (ms)
solid15.15
vue vapor16.74
vue, vdom90.75

Classic Vue does run here, and on this screen its virtual DOM costs about six times the frame work of the same UI without one. Compiled reactivity is why the other two fit in the budget at all.

The three shipped frameworks · hero demo (ms)
solid3.66
vue vapor3.61
octane6.53

Seven samples per app, same 16.67 ms budget. These two charts come from separate runs and the toolchain got faster in between, so read each on its own terms rather than across them.

A markdown editor, built three ways

Apple M3 Max · medians of committed runs
Cold start to first painted frame (ms)
pocket149
tauri v2380
electron301
Idle resident memory (MB)
pocket83
tauri v2193
electron382

On disk the same three are 10, 9 and 242 MB, and only the pocket build runs in a single process. Left alone with a document open, it redraws about twice a second: the caret blinking, and nothing else. The published report also says where the pocket build loses, and why.

Architecture the frame contract

PocketJS makes time the frame counter: one frame(buttons) call is a transaction nothing outside it can interrupt. Nothing waits on a wall clock, so tests run as fast as the CPU allows without changing the timing they measure: a journey that takes six seconds in front of a user is a few dozen frames in CI.

where an effect actually lands
wall clock
network reply arrives mid-frame
input tape
0000000400040000 2000000000010000
frames
n+1+2+3 +4+5+6+7
pixels

A reply that arrives partway through frame +3 is queued, not applied. It is delivered at the start of frame +4, in FIFO order, before any app hook runs. Nothing lands halfway through a transaction: no wall clock, no microtask races, no mid-frame callbacks, and after() replaces setTimeout with a deadline measured in frames.

staten+1 = F(staten, inputn)
pixelsn = G(staten)
every app is a pure fold over its inputs
Which frame the same async task lands on, 60 runs each
wall clock, 60 runs 22 outcomes · assertion 9/60 140 145 150 155 160 165 tallest bar: 6 of 60 runs frame clock, 60 runs 1 outcome · assertion 60/60 140 145 150 155 160 165 frame 144, every run, forever

Same app, same awaited confirmation, same assertion. Driven by requestAnimationFrame against a wall clock it lands on 22 different frames, and the timing assertion passes 9 times out of 60. On the frame clock it lands on frame 144 in every run. That spread on the left is where a flaky test lives: the assertion is really a question about a race.

Thirteen-frame filmstrip of a whole session captured at 2 Hz

SUBSAMPLED · a whole session in 13 frames at 2 Hz, byte-identical to its counterpart in the 390-frame 60 Hz run.

Because history is a data structure, hypotheticals are cheap: fork the tape at frame 9, splice in a different press, re-run the fold, and a counterfactual world exists in 22 ms. Chaos mode proves the floor holds, injecting real sleeps, allocation churn and forced GC between frames without moving the trace by one bit.

Beyond 2D UI mount what the content needs

PocketJS is an application runtime with a game engine's architecture, so a game and an app are built the same way.

one guest, different cores Pocket TalkOpenStrikePocket Voxel
guest programyour JavaScript, one frame at a time
uitree, layout, draw, input, focus
netpoll batches audiopcm mixer strikebsp, bots, hits voxelchunks, meshing

Cores are independent native modules, loaded the way a kernel loads drivers. An app takes the ones its content needs and the rest never enters the build. Adding one widens what your program is allowed to ask for; it never changes how your program is written.

Pocket Talk, a message thread with the system keyboard OpenStrike, a muzzle flash over a crate with a Solid JSX HUD Pocket Voxel, a route of extruded voxel grass and carved trees
Compatibility run on the metal, not emulated

PocketJS has booted on every operating system below, on the real machine, and what changes between them is one native submission layer, never the application. Each entry links to the post or pull request that brought it up. Keeping the hardware bootable is its own work, so the Pocket Museum project repairs these machines and keeps them running.

Ecosystem shipped on the runtime

PocketJS carries complete applications on the machines this runtime was built for: games, a design-file viewer, a desktop companion, a streaming client, and the debugger itself. Each card links to how it was built.

OpenStrike on PSP with a Solid JSX HUD

OpenStrikefps

A Counter-Strike-shaped shooter on 2004 hardware: BSP maps, bots, and a HUD written in Solid JSX. 60 fps, with 2.2 ms of JavaScript per frame and a worst observed frame of 9.7 ms.

Read the story →
Pocket Voxel diorama of a Game Boy town

Pocket Voxelworld

A creature-RPG town rebuilt as a walking voxel diorama. Game state lives in the JS guest; logic runs at 60 Hz while presentation holds a locked 30 fps beat, two ticks per presented frame.

Read the story →
A whole Figma page fit on a PSP screen

Pocket Figmaviewer

A real 14,430-node design file, cooked into streamed tile pyramids and panned with the analog nub at 60 fps on a handheld with 32 MB of RAM.

Read the story →
The VRM character widget beside Activity Monitor

Pocket Characterdesktop

A rigged VRM companion in a transparent always-on-top window, rendering skinned 3D at 60 fps in one process and 118 MB. The Electron build of the same idea takes 8 processes and 2184 MB.

Read the story →
Pocket YouTube search results on a PSP

Pocket YouTubeclient

Search, thumbnails, playback and seeking on a console that predates streaming, where the network is a USB cable and a Mac companion does the fetching.

Read the story →
DevTools panel highlighting the same node as the device screen

Pocket DevToolstooling

Time-travel debugging over a USB cable at 2 bytes per frame. The inspector highlight is emitted by the core into the DrawList, so it renders on the real device, on every backend.

Read the story →
who builds this

Pocket Lab is an independent, non-VC-backed organization built on this runtime, so that the joy of creating belongs to everyone.

Sponsors thank you

We can only develop PocketJS full-time thanks to your support.

Become a sponsor