UI Overview
Everything lives in the electrobun/main/ui entrypoint. It is import-gated:
apps that never import it pay nothing for it.
import { live, signal, ui, createUIWindow } from "electrobun/main/ui";Warren has two renderers over one core: this page covers the GPU
renderer (main process, Dawn); the same reactivity, JSX, and control flow
also render into real DOM inside webviews via
electrobun/browser/ui. Store and
model code written against the primitives runs unchanged on both sides.
How rendering works
Your components build a retained tree of nodes (boxes, text, native-layer anchors). Reactive props are fine-grained: each thunk prop becomes one effect that writes one tree property. A frame tick polls input and — only when something marked the tree dirty — runs layout, flattens the tree into an instance buffer, and issues one instanced draw call to the window’s Dawn surface. Idle UI does no layout, paint, or GPU work; hidden windows skip the tick entirely.
UIWindows are regular GpuWindows underneath —
same native window, same full-window WGPUView, same surface. The UI runtime
is a layer on top, not a different GPU stack.
createUIWindow
const uiWindow = await createUIWindow(options, app);Creates a GpuWindow, mounts the tree, and starts the frame tick. The app
callback builds the UI with the element API.
| Option | Type | Default | Purpose |
|---|---|---|---|
title | string | — | Window title. |
width, height | number | — | Initial content size. |
titleBarStyle | "hidden" | "hiddenInset" | "default" | "hiddenInset" | hiddenInset keeps the window frame equal to the content area. |
transparent | boolean | false | Alpha-composited window. Pair with an alpha background and a rounded root box for floating panels. |
alwaysOnTop | boolean | false | Keep the window above normal windows. |
background | string | "#141420" | Clear color behind the tree. Accepts #rgb, #rrggbb, #rrggbbaa. |
tickMs | number | 8 | Frame tick interval. Input polls every tick; rendering only on dirty ticks. |
Returns { window, context, dispose }:
window— the underlyingGpuWindow(show(),hide(),isVisible(),setPosition(),setSize(),setAlwaysOnTop(), …). Hiding the window suspends all UI work until it is shown again.context— the mount’sUiContext(tree, reactivefocusedId/hoveredId, handler registries). Mostly useful for advanced integrations and tests.dispose()— tears down the reactive scope and closes the window.
createUIView
const uiView = await createUIView(view, options, app);Mounts a tree into an existing WGPUView instead
of a whole window — the mechanism behind
<electrobun-ui> overlays. options accepts
background and tickMs. The view’s frame supplies size and pointer offset.
registerUIRoot
const registration = registerUIRoot("overlay-name", { background: "#1b1b28" }, app);Mounts app into every
<electrobun-ui name="overlay-name"> tag that
appears in your webviews. Each tag instance gets its own tree. Views removed
by the page are detected and disposed. Call registration.dispose() to stop.
Input model
- Pointer — native view events on macOS (move, press, release, wheel,
enter/exit), with a polling fallback elsewhere: hover enter/leave, press,
release, click. Mouse wheel scrolls the innermost
overflow: "scroll"container. A press outside any hittable node blurs focus. Nodes opt in via handlers (onClick,onPointerDown,onPointerUp,onPointerEnter,onPointerLeave). - Keyboard — native key events route to the focused node’s
onKeyDownfirst (bubbling through ancestors; returntrueto stop), then to window-level handlers registered withonKey(handler). On macOS, events carry the keyboard layout’s characters (e.chars). - Focus —
focusable: truenodes take focus on click;textInputautofocuses when asked.ctx.focusedId()is reactive. - Window dragging —
windowDrag: truenodes move the window when dragged; a press without movement still deliversonClick.
Coordinates and units
All sizes and positions are logical points with the origin at the window
content’s top-left. Colors are hex strings (or 0xRRGGBBAA numbers).