Fundamentals
UI Components
Build rich extension UIs with the @oxprotocol/ui component vocabulary.
@oxprotocol/ui provides a frozen V1 component vocabulary for building extension interfaces. Authors compose a tree of typed components; hosts render them natively (Piye) or via the DOM backend (VS Code webview).
The Component Set
OXP V1 ships exactly six components. New components require a spec bump — this vocabulary is intentionally small and stable.
| Component | Purpose | Container |
|---|---|---|
Box | Generic container with padding and gap | ✅ |
Stack | Flexbox-style vertical or horizontal layout | ✅ |
Text | Text content with variant styling | — |
Button | Clickable action trigger | — |
VirtualList | Efficiently render large lists | ✅ |
CodeBlock | Syntax-highlighted code display | — |
Basic Usage
import { Stack, Text, Button } from "@oxprotocol/ui";
import { defineExtension } from "@oxprotocol/sdk";
export default defineExtension({
activate(host) {
host.renderTree(
Stack({ gap: 2 }, [
Text("Hello, OXP!", { variant: "heading" }),
Text("Build once. Install everywhere."),
Button({ label: "Get Started", action: "start", variant: "primary" }),
])
);
},
});Component Reference
Box
Generic container with padding and gap.
Box({ pad: 4, gap: 2 }, [
Text("Inside a box"),
])| Prop | Type | Description | ||||||
|---|---|---|---|---|---|---|---|---|
pad | `0 \ | 1 \ | 2 \ | 3 \ | 4 \ | 6 \ | 8` | Padding (spacing scale) |
gap | `0 \ | 1 \ | 2 \ | 3 \ | 4 \ | 6 \ | 8` | Gap between children |
children | UiNode[] | Child components |
Stack
Flexbox layout — vertical or horizontal.
Stack({ axis: "horizontal", gap: 2, align: "center" }, [
Button({ label: "Save", action: "save" }),
Button({ label: "Cancel", action: "cancel", variant: "ghost" }),
])| Prop | Type | Default | Description | |||
|---|---|---|---|---|---|---|
axis | `"vertical" \ | "horizontal"` | "vertical" | Layout direction | ||
gap | spacing scale | — | Gap between children | |||
align | `"start" \ | "center" \ | "end" \ | "stretch"` | — | Cross-axis alignment |
Text
Text("Hello", { variant: "heading" })| Prop | Type | Description | |||
|---|---|---|---|---|---|
value | string | The text content (first argument) | |||
variant | `"body" \ | "heading" \ | "caption" \ | "code"` | Visual style |
Button
Button({ label: "Run", action: "run-task", variant: "primary", disabled: false })| Prop | Type | Description | |||
|---|---|---|---|---|---|
label | string | Button text | |||
action | string | Action ID sent to the host on click | |||
variant | `"primary" \ | "secondary" \ | "ghost" \ | "danger"` | Visual style |
disabled | boolean | Disable interaction |
VirtualList
Efficiently renders large lists. The host may virtualize based on item count.
VirtualList({
items: data.map(item => Text(item.name)),
rowHeight: 28,
})CodeBlock
CodeBlock({ value: "const x = 42;", language: "ts" })Tree Validation
The validateTree() function checks that every node in a tree uses a known V1 component kind. It's used by oxp pack to reject bundles with non-V1 nodes, and by hosts as a runtime guard.
Render Modes
| Mode | Description |
|---|---|
| DOM renderer | @oxprotocol/ui/dom — renders the tree to real DOM elements in a webview. Used by the VS Code host. |
| Native renderer | Host implements the component set in its native toolkit. Used by Piye (GPUI). Same .oxp bundle, 120fps native UI. |
The beauty of the component vocabulary is that your extension doesn't need to know which renderer is active. The same tree works everywhere.