Write once. Render anywhere.
Uniview is a universal renderer. Author plugins in React or Solid and render them to real Svelte / Vue / React DOM, a terminal, or a native macOS window — across an RPC boundary.
<Stack> <Text>Counter</Text> <Text>{count}</Text> <Button onClick={inc}>+1</Button> </Stack>
One tree, many targets
A plugin never touches a DOM. It emits a serializable UINode tree and a stream of mutations. Each host is simply a renderer for that tree — in its own native primitives.
The primitive set is bounded and converges; an app's UI is unbounded and grows forever. That asymmetry is the whole bet — a new plugin framework is a new renderer package and zero protocol change.
See what it renders
Every screenshot below is an actual frame of the real component tree — captured to SVG by swapping a single line (AnsiCellSurface → SvgCellSurface). Each is pure plugin code, rendered by the terminal host. Click any card to run it.
Gauge, bar chart, histogram and live stats.
A braille Canvas waveform driven by a per-frame loop.
Full-color rasters as half-block ▀ cells — two pixels per cell.
A virtualized, sortable table with regex find and filter.
Multi-pane browse with a collapsible $ref schema tree.
A playable board plus an n-tuple network over expectimax.
The same tree, on every surface
One authoring model, drawn natively wherever it lands. Adding a surface is a renderer package — it never changes the plugin or the protocol.
DOM hosts
Svelte 5, Vue, and React adapters render the tree into real DOM. High-frequency interaction — scroll, hover, focus — stays local, never streamed over RPC.
Terminal (TUI)
The same plugin renders to a terminal with no DOM at all — layout, styled text, and sub-cell-resolution charts, from React or Solid.
Native macOS (AppKit)
A real NSWindow and AppKit views, driven from a React tree — native bezel buttons, vibrancy, scroll views. Windows and HarmonyOS are next.
Sandboxed by default
Plugins never touch the page they render into. They run behind a process or worker boundary and speak to the host only through typed RPC.
Web Worker
The production path for untrusted plugins. No access to the page — no window, no document.
WebSocket bridge
Plugins connect out to a bridge; a plugin can run in another process, or on another machine entirely.
Main thread
Zero isolation for fast local debugging. The host code is identical across all three modes.
Same host code, three transports — read the runtime-modes guide.
A renderer with no opinions of its own
The renderer is reimplemented on every platform, so it stays small and stays neutral. It renders what the tree says — nothing it decided on its own.
Framework-agnostic
The protocol and every host speak UINode + Mutation + Style IR. They don't know React or Solid or Svelte exists. A new plugin framework is a new package — nothing else changes.
App-agnostic
The renderer has no idea what a sidebar or a command palette is. Those are components, written in TypeScript in the plugin — never primitives baked into the core.
Brand-agnostic
No color, gradient, radius, or shadow the renderer invented. It draws the Style IR the plugin sends. Semantic tokens resolve to the system's colors — the user's, never ours.
Your first plugin in minutes
Write an ordinary component. Uniview handles the tree, the transport, and the render — the same source runs on every host.
- 1Author a component tree in React or Solid.
- 2The renderer serializes it to a UINode tree — no DOM involved.
- 3Boot the plugin in a Worker, over the bridge, or on the main thread.
- 4A host renders it to DOM, a terminal, or a native window.
import { useState } from "react"import { Stack, Text, Button } from "@uniview/react-runtime"export default function Counter() { const [count, setCount] = useState(0) return ( <Stack className="p-4 gap-2"> <Text>Counter</Text> <Text>{count}</Text> <Button onClick={() => setCount((c) => c + 1)}>+1</Button> </Stack> )}A protocol-first toolkit
The plumbing real plugins need — typed RPC, a portable style model, and renderers you can add without touching the core.
Type-safe RPC
Built on kkrpc — bidirectional, typed calls over Worker, WebSocket, HTTP, or stdio transports.
Style IR
Tailwind classes resolve to a serializable style tree each host paints natively — including dark:/hover:/focus: variants.
React + Solid renderers
Two authoring frameworks today, each a small renderer package. The protocol never learned either one exists.
Sub-cell charts
The terminal renderer draws charts at sub-cell resolution and can export them to SVG — ride the richtext spans, no new primitive.
Handler-registry events
Functions never cross RPC. Callbacks register as handler IDs and dispatch back to the plugin on interaction.
Protocol versioning
A single PROTOCOL_VERSION is checked on the initialize handshake, so hosts and plugins fail loud, not silently.
Build your first plugin
Start with the quickstart, or read the architecture that makes one tree render across DOM, terminal, and native hosts.