VibeXR Interaction Framework

One interaction model for hands, controllers, gaze and mouse

VibeXR is the WebXR interaction framework I build my browser XR prototypes on. It is a single HTML file on three.js: open it in a headset browser, tap Enter, and you are in passthrough with working hands. Hands, controllers, gaze pointers (Vision Pro) and a desktop mouse all drive the same small set of verbs: point, pinch, poke and grab, near or far.

It grew out of vibe coding: I wanted every new idea to start from interaction that already feels right, instead of rebuilding rays, grabs and buttons for each prototype. The tuning comes from my XR OS interaction work: when the ray appears, how far a far grab travels, how big the poke volume is, what a hover outline looks like on a messy CAD mesh. Projects then supply only their own world.

Try it

Interaction Playground

Open it in a PICO, Quest, Galaxy XR or Vision Pro browser and tap Enter for passthrough. On desktop the playground runs as a preview: left-click is a ray pinch, the wheel pushes and pulls while dragging, right-drag orbits.

Open live demo ↗

Try the beta version ↗ · the next release, still being tested: keyboard and mouse, Unity-style on desktop (right-drag to look, right + WASD / QE to fly, middle-drag to pan, Alt + left to orbit, F to frame) and in XR.

Built on VibeXR

Each of these is one world on the same framework, so the same hands, controllers, gaze and mouse work in all of them. Open them in a headset browser and tap Enter.

Dream Cage: a glass cube on a desk; its left face shows a night town, its right face a garden by day, and a moon and a red balloon meet across the edge between them.

Dream Cage

A diorama-sized portal cube in the spirit of Moncage. Each of its six faces opens onto its own small world. Turn the cube in your hands until half a shape on one face meets its other half on the next, making one whole shape, and the two worlds join.

Open live demo ↗

Microduck XR

Pollen Robotics' Microduck with its real walking policies running in MuJoCo in the browser. Pick it up, send it walking, make it kick.

Open live demo ↗

Immersive Vibe Gallery

Walk into painted worlds: Starry Night, Impression Sunrise, a Qianli Jiangshan scroll and living ink.

Open live demo ↗
Robot Zoo XR: the white Unitree G1 dancing beside the Unitree B2 robot dog walking across Microduck XR's line-drawn stage.

Robot Zoo XR

Open-source robots running their published policies in MuJoCo: Microduck, Unitree G1 dancing and flipping, G1, H1 and H1-2 walking, Booster T1, AgiBot X1, Berkeley Humanoid Lite, and three robot dogs: Unitree Go2, Unitree B2 and AgiBot D1. They share Microduck XR's stage: life-size on your floor, walking through your room, or on a tabletop.

Open live demo ↗
Archiscape: the town rebuilt as the Forbidden City, vermilion halls under golden roofs on a round plate, the era panel beside it.

Archiscape

A Townscaper-style town builder for your table. Tap to build, then rebuild the same town in nineteen eras, from Stonehenge and Hagia Sophia through the Forbidden City, Jiangnan after Wu Guanzhong, Kyoto, the Kremlin and the Thai temples to Gaudí, Le Corbusier, I. M. Pei and Hadid, each with its own generative music.

Open live demo ↗
Physics Cabinet: a ball of water pulled apart by two hands into three lobes, floating over the plate.

Physics Cabinet

Ten experiments to pick up and play with: a water ball, Newton's cradle, chaos pendulums, cloth, jelly, a spinning top, a Galton board, orbits and the magnetic pendulum.

Open live demo ↗

Features

Build on it

A project is a world: an object with init, enter, update and exit. The world context gives it a scene root and helpers that register interactables, so everything the world creates is cleaned up when it unloads.

const Hello = {
  id: "hello",
  title: "Hello VibeXR",
  init(ctx) {
    const { root, THREE } = ctx;
    root.add(new THREE.HemisphereLight(0xffffff, 0x222222, 2));   // a world brings its own lights
    const cube = new THREE.Mesh(new THREE.BoxGeometry(0.1, 0.1, 0.1), new THREE.MeshStandardMaterial({ color: 0x7fb2ff }));
    cube.position.set(0, 1.3, -0.5);
    root.add(cube);
    ctx.add(cube, { draggable: true, twoHand: true })   // grab near or far; two hands rotate and scale
       .on("tap", () => ctx.log("tapped"))
       .on("longpress", () => ctx.log("held"));
    const reset = ctx.button("Reset", { onClick: () => cube.position.set(0, 1.3, -0.5) });
    reset.root.position.set(0, 1.15, -0.45);
    root.add(reset.root);
  },
  update(dt, t, ctx) {},
};
app.worlds.register(Hello);

Projects built on VibeXR:

Changelog

The current version is v0.8.25. The full history is at the top of the source file.