FRAMEWORK / BUNNY_UI · EARLY DEVELOPMENT

One codebase. Every surface.

bunny_ui is a declarative UI framework for Rust, inspired by SwiftUI. Write views as value types. The framework finds the views that read changed state and runs only those.
view the source →
read the docs
rust, end to end · std only · the api is not stable
QUICK LOOK

A tap re-runs one view. Not your app.

The display of count records a read. A tap changes the state, and the framework runs only this view again.
00
One codebase, five targets
iOS, Android, macOS, Linux and the web from a single Rust crate. No bridges, no JS runtime, no second team keeping the ports honest.
01
Fast is the default
No virtual DOM, no diffing the world. A render pass runs only the views that read changed state.
10
GPU-accelerated renderer
The desktop composites every frame on the GPU, and the web can too. Same rasterizer everywhere — your pixels agree byte for byte.
11
Zero bloat
The framework crates use only the Rust standard library. Nothing to audit, nothing to carry, nothing between you and the frame.
counter.rs
cargo run · ✓
#[derive(Clone, Copy)]
struct Counter {
count: State<i32>,
}
impl Component for Counter {
fn body(self, _ctx: &Context) -> impl View {
vstack!(
text!("Count: {}", self.count),
button(text("Tap"), move || self.count.add(1)),
)
}
}
TARGET 01
iOS
bunny_ui_ios · metal
TARGET 02
Android
bunny_ui_android · vulkan
TARGET 03
macOS
bunny_ui_macos · metal
TARGET 04
Linux
bunny_ui_linux · vulkan
TARGET 05
Windows
bunny_ui_windows
TARGET 06
Web ×3
canvas · dom · gpu tier
one tree of plain rust values · one cargo build · the pixels agree byte for byte
01 — WORK THAT WAITS

A view can own asynchronous work.

.task starts it on the view's first appearance and ends it when the view leaves the tree.
The framework reads no file and opens no socket. The application does that on its own thread — or through its own browser callback — and hands the results over a channel. The sender is the only part that crosses a thread boundary, and it carries a signal, not a scene: the shell answers with the frame it already knows how to draw.
Cancellation is a drop. A view that leaves the tree ends its task, the reader dies, and the next send answers Err — the sign for the worker to stop. .task_id(id) restarts the work when the id moves, so a details panel that switches files cancels the read in flight.
log_view.rs
row.task(move || async move {
let (lines, reader) = task::channel();
std::thread::spawn(move || read_the_log(lines));
while let Some(line) = reader.recv().await {
log.update(|all| all.push(line));
}
})
custom.rs
// the short door: a box that only draws
canvas(|ctx, p| p.fill_rounded(ctx.bounds(), ink, 6.0))
// the full one: it measures, paints, and answers the pointer,
// the keyboard and the input system
custom(SketchPad { strokes, caption })
02 — A BOX THE APPLICATION OWNS

Some content has no interface vocabulary.

A code editor, a terminal grid, a waveform. It gets a box of its own, painted with the same commands every built-in view emits.
The box paints in its own coordinates and cannot escape them — the clip around it is the framework's. It hears how much of it the clip lets through, so a long document costs one screen, and it inherits the ink and the font of the scope above it.
Nothing forks: the desktop composites the box on the GPU, the web canvas mode on the CPU, and the element mode turns it into a canvas island.
use it for content that has no views. a rounded corner, a hover state or a gradient belongs in the framework.
03 — PAINT

Gradients, clipping, icons. Declared once, agreed everywhere.

Gradients
A two-stop ramp is a property of a view, declared in the box's own proportions so it survives every resize. The placement resolves it to pixels once; the rasterizers only evaluate.
panel.background_gradient( Gradient::radial(violet, violet.fade()) .center(UnitPoint::TOP) .radius(0.0, 420.0), )
Clipping
.clipped() cuts the subtree to the box — and the cut follows the .corner_radius(…) already on it. There is no radius to repeat and no order to remember: the two fuse into one node.
vstack((toolbar(), panels())) .background_color(surface) .border(outline, 1.0) .corner_radius(6.0) .clipped()
Icons
A glyph is a recipe, never pixels: verbs on a fixed 24 grid, plus the paint that turns contours into ink. Crisp at sixteen, crisp at sixty-four.
icon(symbol::CHEVRON_RIGHT) icon(symbol::SEARCH).font(Font::Title) icon(symbol::FOLDER) .resizable().frame(24.0, 24.0) icon(acme::LOGO)
sixteen symbols ship with the framework (bunny_ui::symbol). an app converts its own icon files offline — the tool prints rust const data to paste into the app; the default build carries no parser.
04 — DESIGN RULES

Four rules. No exceptions.

  1. 01
    The framework crates use only the Rust standard library.
  2. 02
    Views are plain values. State lives in typed arenas behind small handles.
  3. 03
    A render pass runs only the views that read changed state.
  4. 04
    The layout protocol is a proposal from the parent and a response from the child.
05 — STATUS

Early development. The API is not stable.

It compiles on your machine. That is the whole install.
build and test
$ cargo build
$ cargo test
$ cargo test --features svg # the icon converter's parser rides the flag
DEMOS
cargo run -p bunny-ui --example counter_headless
Prints a small interface to the terminal.
cargo run -p bunny-ui-macos --example counter_window
Opens a native macOS window.
cargo run -p bunny-ui-macos --example git_window
Reads this repository's own git log from a worker thread and fills the window while it scrolls.
cargo run -p bunny-ui-macos --example sketch_window
One box the application owns: draws its own ink with the pointer, sizes its brush with the wheel, types into a caption of its own.
cargo run -p bunny-ui-macos --example icon_window
The sixteen house glyphs across fonts and inks.
cargo run -p countries-pure
A full sample application.
BUILT WITH IT — TRINITY

The editor is built in the framework it edits.

Every panel, every canvas, every cursor in Trinity is bunny_ui rendering itself. A native, GPU-rendered workbench. Rust end to end, no Electron.