Open-source · protocol-first · framework-agnostic

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.

DOM hosts (Svelte · Vue · React)Terminal (TUI)Native macOS (AppKit) · newWindows · HarmonyOS · planned
Plugin · React or Solid
<Stack>
  <Text>Counter</Text>
  <Text>{count}</Text>
  <Button onClick={inc}>+1</Button>
</Stack>
DOM host
Counter
3
Terminal (TUI)
┌─ Counter ─────┐
3
+1
└───────────────┘
Native macOS · new
Counter
3
How it works

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.

Plugin
React / Solid
Reconciler
→ UINode tree
Mutations
over RPC
Host renderer
DOM · TUI · AppKit

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.

Isolation

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

Browser · full sandbox

The production path for untrusted plugins. No access to the page — no window, no document.

WebSocket bridge

Node · Deno · Bun

Plugins connect out to a bridge; a plugin can run in another process, or on another machine entirely.

Main thread

Browser · dev only

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.

The Prime Directive

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.

Quickstart

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.

  1. 1Author a component tree in React or Solid.
  2. 2The renderer serializes it to a UINode tree — no DOM involved.
  3. 3Boot the plugin in a Worker, over the bridge, or on the main thread.
  4. 4A host renders it to DOM, a terminal, or a native window.
Read the full guide
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>  )}
And more

A protocol-first toolkit

The plumbing real plugins need — typed RPC, a portable style model, and renderers you can add without touching the core.

Build your first plugin

Start with the quickstart, or read the architecture that makes one tree render across DOM, terminal, and native hosts.