Native desktop applications in Elixir, rendered by GPUI.
GPUI keeps application state, window topology, UI declarations, and event
handling in Elixir. Rust owns the native event loop, windows, rendering, focus,
IME, accessibility, and latency-sensitive interaction. Conventional controls
are provided separately by gpui_components
and rendered with
gpui-component.
Elixir owns the application. GPUI owns the native interaction. Snapshots connect them.
One declarative application can run against a local native display, a deterministic test display, or a native display on another machine.
GPUI supports precompiled Linux, Apple silicon macOS, and x86-64 Windows hosts. See Platform support and development status.
defmodule CounterView do
use GPUI.View
alias GPUI.UI
def render(assigns) do
~GPUI"""
<div class="flex grow flex-col items-center justify-center w-full gap-4 bg-slate-950">
<text class="text-3xl font-semibold text-white">Count: {assigns.count}</text>
<UI.button id="increment" label="Increment" variant="primary" phx-click="increment" />
</div>
"""
end
def handle_event("increment", _event, assigns) do
{:noreply, %{assigns | count: assigns.count + 1}}
end
end
defmodule CounterApp do
use GPUI.Application
def mount(_args) do
{:ok,
[
window "Counter" do
size(420, 280)
root(CounterView, count: 0)
end
]}
end
endassigns remains authoritative Elixir state. Pointer, keyboard, and accessible
activation return through the same typed event path; Rust does not become a
second application state system.
Run the progressive examples from the repository:
RUST_FONTCONFIG_DLOPEN=1 mix run apps/gpui/examples/getting_started/01_hello_window.exs
RUST_FONTCONFIG_DLOPEN=1 mix run apps/gpui/examples/getting_started/02_events.exs
RUST_FONTCONFIG_DLOPEN=1 mix run apps/gpui/examples/getting_started/03_supervised_updates.exs
RUST_FONTCONFIG_DLOPEN=1 mix run apps/gpui/examples/getting_started/04_controlled_form.exs
RUST_FONTCONFIG_DLOPEN=1 mix run apps/gpui/examples/getting_started/05_multiple_windows.exsSee Your first application for supervision, controlled components, event handling, and native prerequisites.
- Native, not a web view. GPUI provides native layout, text shaping, windows, focus, platform input, and AccessKit semantics without a DOM or embedded browser runtime.
- Declarative, not raw Rust bindings. Elixir renders bounded, serializable snapshots; displays interpret them and return strict typed events.
- OTP remains the application runtime. Controlled values stay in Elixir. Native code retains only interaction mechanics such as focus, IME, text selection, drag sessions, and measurement.
GPUI.Application
│
▼
GPUI.WindowSpec
│
▼
GPUI.Session ──────► GPUI.Snapshot
│
┌────────────┼────────────┐
▼ ▼ ▼
Native display Test display Remote display
A native display presents real windows. A test display makes view behavior deterministic without Rust or a display server. The remote protocol can keep the authoritative session on one machine while another presents its native windows. Dynamic multi-window topology remains snapshot-driven in every mode.
See Sessions, snapshots, and displays, Remote displays, and Testing GPUI applications.
- Persistent native text: Rope buffers, zero-based UTF-16 positions,
revisioned atomic transactions, plural selections, undo/redo, IME, shaped
style runs, and asynchronous geometry.
<text_surface>supports editors and auto-growing composers without moving document policy into Rust. - Rich virtualized content: variable-height collections preserve scroll anchors while items are prepended or remeasured; selectable rich text adds native shaping, clipboard behavior, and keyboard-activatable opaque links.
- Native semantics across displays: accessibility roles and states, stable identities, bounded clipboard/file/drop values, and unified pointer, keyboard, and AccessKit activation all travel through renderer-independent snapshots and typed events.
- A small handwritten native core: RustQ generates Rustler declarations, decoders, renderer and style dispatch, and registry glue from the Elixir schema. Handwritten Rust is reserved for platform integration, reconciliation, and latency-sensitive native mechanics.
Read more in Editable text internals, Accessibility internals, and UI components.
| Example | What it demonstrates |
|---|---|
| Rich transcript | Variable-height virtualization, selectable rich text, links, and a native composer |
| BEAM Control Room | Supervised runtime sampling driving controlled native views |
| Image Lab | Display-side file reads, image decoding, and bounded raster resources |
| Component Gallery | Canonical component states, combinations, and accessibility behavior |
Run the gpui.dev Mix task with the larger examples for state-preserving
Elixir source reload through GPUI.Dev.Reload. See the examples index for the
complete catalog.
The repository is a Mix umbrella with three independently publishable packages and one private maintainer application:
| Package | Purpose |
|---|---|
gpui |
Renderer-independent applications, sessions, snapshots, schemas, remote displays, and test APIs |
gpui_components |
Conventional declarative controls backed by gpui-component |
gpui_native |
RustlerPrecompiled vanilla and gpui-component native hosts |
| Root tooling | Private RustQ generation, repository checks, native testing, and release validation |
RustQ runs only in the source umbrella. Generated Elixir and Rust are committed,
and supported consumer targets download checksum-verified NIF archives through
RustlerPrecompiled; consumer compilation does not run RustQ.
For a renderer-independent application or remote server:
def deps do
[
{:gpui, "== 0.2.0"}
]
endFor a native application using conventional controls, gpui_native brings the
matching gpui and gpui_components versions transitively:
def deps do
[
{:gpui_native, "== 0.2.0"}
]
endSelect the gpui-component host when component elements are used:
# config/config.exs
config :gpui_native, GPUI.Native, host: :gpui_componentUse host: :vanilla for the complete vanilla-GPUI host. Exactly one host
artifact is loaded; the component-capable artifact statically links its
renderer rather than attaching a second native library at runtime.
Renderer-independent sessions, remote servers, and GPUI.Test
require neither Rust nor a native library or display server. See
Native builds and deployment.
- Documentation home
- Your first application
- UI components
- Templates and elements
- Forms and controls
- Collections and data views
- Text and editing
- Editable text surfaces
- Layout, styling, and presentation
- Presentation primitives
- Commands and keyboard shortcuts
- Overlays and menus
- Testing GPUI applications
- Application tests
- Deterministic native tests
- Desktop E2E and visual evidence
- Platform support and development status
- API reference
mix deps.get
mix ci
MIX_ENV=e2e xvfb-run -a dbus-run-session -- mix test --only e2e apps/gpui_native/test/e2emix ci is the development gate: it covers Elixir, generated Rust freshness,
Cargo feature matrices, Clippy, native tests, Credo, duplication, and architecture
checks. It does not rebuild package consumers or package documentation.
For package-boundary changes, run mix gpui.test.packages: it builds the three
public Hex payloads, checks both native source graphs, and compiles the exact
unpacked contents in clean downstream consumers with repository-only tooling
and Cargo unavailable. Before publishing, run mix ci followed by
RUST_FONTCONFIG_DLOPEN=1 mix gpui.release.check; the release gate includes
package isolation, documentation, dependency audits, and license checks.
Package validation deliberately uses fresh consumers rather than persistent
build caches.
Hosted push validation splits the renderer-independent and native
checks; the full native interaction and release-package suite is an explicit
Release candidate workflow rather than a scheduled or per-push job. See
Testing GPUI applications for native
Xvfb/Lavapipe interaction coverage.
MIT