Embeddable UI
Drop a Lifo terminal and a live preview browser into your own app with the framework-agnostic @lifo-sh/ui package.
@lifo-sh/ui gives you the playground's building blocks as reusable, framework-
agnostic components — mount them into any DOM element, wire them to a box, done.
No React required (though they work fine inside it).
Terminal— a themeable xterm.js terminal bound to a box's shell.PreviewBrowser— an iframe view bound to an in-VM port, with optional Chrome-like chrome (back / forward / reload, a friendly address bar, and open-in-new-tab).FileExplorer— a file tree over the box's filesystem (optionally with an editor).
npm install @lifo-sh/core @lifo-sh/uiRun a project from scratch
Here's a complete, minimal app: boot a box, attach a terminal, start a server inside the VM, and show it live in a preview browser — all in the browser, no backend.
<div id="terminal" style="height: 300px"></div>
<div id="preview" style="height: 400px"></div>import { Sandbox, ServiceWorkerBridge } from "@lifo-sh/core";
import { Terminal, PreviewBrowser } from "@lifo-sh/ui";
// 1. Boot a box with a terminal attached. The terminal is wired to the box's
// shell — you get an interactive prompt immediately.
const term = new Terminal(document.getElementById("terminal")!);
const sandbox = await Sandbox.create({
terminal: term,
cwd: "/home/user/app",
files: {
"/home/user/app/server.js": `
const http = require("http");
http.createServer((req, res) => {
res.setHeader("content-type", "text/html");
res.end("<h1>Hello from a Lifo box</h1>");
}).listen(3000, () => console.log("listening on :3000"));
`,
},
});
// 2. Route the box's in-VM ports through a service worker so they can be
// previewed in an iframe. (Serve the Lifo service worker at /sw.js — see
// Live previews.)
const bridge = new ServiceWorkerBridge(sandbox.kernel.portRegistry);
await bridge.connect("/sw.js", "/");
// 3. Start the server. It's long-running, so don't await it — it keeps serving
// until the box is torn down.
sandbox.commands.run("node server.js");
// 4. Mount a live preview bound to the in-VM port.
new PreviewBrowser(document.getElementById("preview")!, { bridge, port: 3000 });That's the whole thing. The preview renders whatever the in-VM server returns,
and any client-side navigation or HMR flows back through the bridge. Swap
server.js for a real project — seed a Vite or Expo app, run
npm install && npm run dev in the terminal (or via sandbox.commands.run), and
point PreviewBrowser at the dev server's port.
Note
Live previews need the Lifo service worker served at /sw.js with a
Service-Worker-Allowed: / header so it can control the whole origin. See
Live previews for how the service-worker bridge works and
Protocols for the wire format.
Terminal
const term = new Terminal(container, {
fontSize: 14, // default 14
webgl: true, // WebGL renderer (default true)
theme, // an xterm ITheme (defaults to Tokyo Night)
});Pass it to Sandbox.create({ terminal: term }) to bind it to the box's shell.
Methods include write, writeln, onData, focus, fit/refit (call
refit() when a hidden terminal becomes visible), setFontSize, and setTheme.
Sandbox.create also accepts an HTMLElement or a CSS selector directly and
creates the terminal for you.
PreviewBrowser
const preview = new PreviewBrowser(container, {
port: 3000, // in-VM port to preview (required)
bridge, // the box's ServiceWorkerBridge (preferred)
// boxId: "box_…", // or an explicit box id, if you manage the SW yourself
path: "/", // initial in-app path (default "/")
chrome: true, // show back/forward/reload + address bar (default true)
});It points an iframe at `/_sw/<boxId>/<port>/` (the service-worker
route) and reads the box id from the bridge. Methods:
reload()— reload in place, preserving the current in-app route.navigate(path)— go to a different in-VM path.setPort(port)— point at a different in-VM port.back()/forward()— drive the iframe's history.route()— the current absolute in-VM route.destroy()— tear down (stops URL polling, removes the DOM).
Set chrome: false for a bare iframe with no browser chrome — useful when you
want to supply your own controls.
Theming
The components are styled with CSS variables (--tk-*, Tokyo Night by default).
Override them on a parent element to reskin — e.g. --tk-bg, --tk-border,
--tk-fg, --tk-blue. The Terminal additionally takes an xterm theme option.
Tip
These are the same building blocks the demo playground is made of. Richer browser chrome (tabs, history) is on the roadmap; the core terminal and preview views are here today.