fenestra
A pure-Rust native GUI framework with web-grade aesthetics — and first-class headless rendering, so both humans and AI coding agents can see what they build.
| Light | Dark |
|---|---|
![]() |
![]() |
No browser. No webview. No HTML or CSS parser. fenestra draws everything itself with vello on wgpu, lays out with taffy (flexbox + grid), shapes text with parley, and ships a themed widget kit that looks like a polished modern web app: layered soft shadows, OKLCH color ramps, real typographic hierarchy, hover/focus transitions, and first-class light and dark themes.
Quickstart
use *;
cargo add fenestra, paste, cargo run. The whole view is rebuilt, laid
out, and repainted on every redraw — no diffing, no macros, everything
autocompletes.
Agents can see what they build
Rendering (element tree, theme, size) to pixels is a pure function, and it
runs without a window or display server:
use ;
// A picture of any element tree:
let image = render_element;
image.save?;
// Or drive a full app with scripted input and look at the result:
let image = render_app;
assert_eq!;
Headless rendering is deterministic (embedded fonts, fixed scale, reduced motion), which makes pixel-exact golden tests practical — fenestra's own widget kit is tested this way, on CI, with no GPU display attached.
Philosophy: web aesthetics without the web platform
The web's look — soft elevation, tinted neutrals, OKLCH ramps, 4px-grid
spacing, focus rings, 120–300ms easing — is the best-tested visual language
in software. The web platform is a heavy way to get it. fenestra encodes
that language as typed Rust values: a Theme generated from one accent hue,
spacing/radius/shadow/motion tokens, and a builder vocabulary (row(),
.p(SP4), .rounded(R_MD), .shadow(ShadowToken::Sm)) small enough to
memorize and regular enough for rust-analyzer (or a language model) to
autocomplete. Every widget routes every color through the theme; flip one
Mode and the whole app is dark.
The kit
Button, IconButton, Checkbox, Switch, Radio, Slider, TextInput (parley editing, clipboard, IME), TextArea (multiline, auto-growing), Select, Tooltip, Modal (focus trap + backdrop), Toasts, Tabs, Card, StatCard, Badge, Avatar, Divider, Progress, Spinner, Table, Callout, and a vendored Lucide icon subset — every state, both themes:
![]() |
![]() |
![]() |
![]() |
Regenerate this corpus any time with cargo run --example gallery — it
renders headlessly.
Workspace
| Crate | Role |
|---|---|
fenestra |
Facade: prelude, run(), examples |
fenestra-core |
Element IR, theme/tokens, layout, text, paint, input, transitions |
fenestra-shell |
winit + wgpu window runner and the headless renderer |
fenestra-kit |
The themed widget kit, built only on core's public API |
See ARCHITECTURE.md for how the pipeline, widget identity, transitions, and overlays work — recorded decision-by-decision as the framework was built.
Composition, commands, accessibility
Components written around their own message type compose with
Element::map. Background work flows in through App::init, which hands
the app a cloneable Proxy<Msg> — spawn a thread, send messages, the
window repaints (examples/clock.rs, examples/toasts.rs). Every widget
exposes its role, state, and name: headlessly via Frame::access_tree()
(assert your UI is labeled, in CI), and to real assistive technology
through AccessKit in the windowed runner. Ambient motion comes from
looping Keyframes timelines; images from image_rgba8 (round avatars
via .rounded_full()).
Status
0.2 covers the spec'd milestones M0–M7 — rendering, theme, text, layout
and scrolling, interactivity and transitions, text input, overlays, the
full kit, the dashboard — plus the M8 set: Element::map, the command
proxy, images, multiline text areas, toasts, keyframe timelines, a Lucide
icon subset, and the AccessKit tree. Out of scope so far: the
screen-reader text-editing protocol, live regions, and rich text.
License
MIT or Apache-2.0, at your option. The embedded Inter font is licensed
under the SIL Open Font License 1.1; the vendored Lucide icon path data is
ISC (see fenestra-kit/LICENSE-LUCIDE.txt).





