⚠️ This is an experimental project to test out various unsupervised llm workflows. The code is largely unreviewed, and should not be used in production
An accessible, web-quality UI library for the Bevy game engine.
▶ Try the live demo — the full widget gallery running in your browser (WebGPU, WebGL2 fallback)
Buiy is a comprehensive, AccessKit-first UI toolkit built as a parallel UI stack to bevy_ui — not on top of it. It wires the same primitives Bevy uses — Taffy for layout, cosmic-text for shaping and editing, AccessKit for the accessibility tree, bevy_picking for hit-testing — behind its own decomposed, ECS-native component model and a custom wgpu render pipeline that runs as a system in Bevy's Core2d render schedule (Bevy 0.19 removed the old render-graph node API). The north star is modern-web feature parity — CSS layout and styling, complex text with BiDi/IME, ARIA roles, WCAG 2.2 AA — for both game and application UIs, with every machine-testable claim gated in CI.
Status — pre-0.1 (
0.0.1), pre-alpha. The subsystems below are built and tested, but APIs are unstable and may break in any commit. Buiy is not yet tagged or published to crates.io. Some GPU draw paths are verified on a real wgpu adapter behind#[ignore]tests, and a handful of CSS sub-features are deferred (called out below). There is a real widget catalog — Button, an editable TextInput, Checkbox, Switch, Slider, Disclosure, Dialog, Tooltip, Popover, Menu, and ScrollArea — exercised by a runnable widget gallery, but the catalog is still narrower than a mature toolkit and the surface is in flux.
- A genuine CSS-subset layout engine over Taffy — flexbox, grid, writing modes, container queries (with multi-level descendant invalidation), anchor positioning, sticky / table / multi-column, transforms + containment, stacking + top layer,
position: fixed, andcontent-visibility. Far more of the CSS box thanbevy_uiexposes. - A custom wgpu render pipeline wired into Bevy's
Core2drender schedule — instance-buffered quads with damage retention and keyed partial re-extract (an interactive change re-resolves and re-uploads only the one changed instance, not the whole scene — proven pixel-identical on a real GPU), per-node radius clipping, an off-screen effect compositor (opacity / isolation / filters / blend modes / backdrop-filter), a shared LRU glyph/image atlas, and a statically-verified forced-colors (high-contrast) path. - First-class text via cosmic-text — shaping, line layout, BiDi, font fallback, an embedded default font, decorations, and a Taffy measure seam with a steady-state zero-remeasure guarantee.
- Text editing — caret and selection model, platform-aware keymap, pointer selection, clipboard, undo/redo with composition-aware grouping, and IME pre-edit composition spliced into the live buffer — surfaced as an editable
TextInputwidget (single/multi-line, placeholder, focus-on-click, submit-on-Enter, caret auto-scroll). - MVU state management in the core — every stateful widget routes its state changes through one ordered, recordable message funnel (
Model/Msg/ pure reducer /Cmd), withenqueueas the single sanctioned mutation and one drain as the sole writer. A recorded session of real input replays byte-identically, including widget-internal state (toggle values, a menu's open index, the editor buffer + caret + selection) that an app-boundary log can't reach. - A real widget catalog + a live gallery — accessible, keyboard-driven Button, TextInput, Checkbox, Switch, Slider, Disclosure, Dialog, Tooltip, Popover, Menu, and ScrollArea (plus composites: progress meter, table rows, search input, kbd chip, status dot), each authorable three ways — a bundle constructor, a
bsn!scene-fn, or in the unified widget gallery (five screens: TodoMVC, a 1,000-row virtual list, an overlay menu, a modal dialog, and a controls showcase). - Declarative
bsn!authoring — the thinbuiy_bsncrate re-exports Bevy 0.19'sbsn!/bsn_list!scene macros and spawn-ext traits, folded intobuiy::prelude, so widget trees can be authored declaratively against the same decomposed components. - Runs in the browser (WebGPU + WebGL2) — the same custom wgpu pipeline compiles to
wasm32-unknown-unknownand renders into an HTML<canvas>; the full five-screen gallery runs and is mouse-interactive (try it live). It ships both a WebGPU build and an unflagged WebGL2 reach build (any modern browser, no experimental flags). Browser screen-reader a11y, IME, and the mobile soft-keyboard are named and deferred, not silently shipped. - Accessibility is foundational, not bolted on — decomposed
A11yRole/A11yLabel/A11yDescriptioncomponents feed a per-frame AccessKit tree pushed to real per-window adapters, so screen readers see the live UI. The same tree is read by an in-process driver that backs both tests and (opt-in) LLM-agent inspection. Plus a CI a11y-snapshot gate and a WCAG-2 contrast linter. - Verification as a deliverable — a dedicated
buiy_verifycrate, a perceptual-diff golden harness, and an honest CI-vs-GPU gate split.
Everything below is real and shipping unless flagged (partial) — a sub-feature warns-once and is deferred — or (GPU-deferred) — the CPU logic is implemented and tested but the wgpu draw runs only on a real adapter behind an #[ignore] test.
| Area | What's there |
|---|---|
| Flexbox & Grid | Full axis / wrap / justify / align; grid templates, named areas, auto-flow, auto-tracks |
| Box model | Padding / margin / border edges, box-sizing, min/max, aspect-ratio, logical edges |
| Positioning | relative / absolute / fixed, inset & logical inset |
| Writing modes | Horizontal & vertical modes, LTR/RTL direction, text-orientation, unicode-bidi |
| Container queries | Size queries with active/inactive markers and multi-level descendant invalidation, capped at 2× Taffy/frame |
| Anchor positioning | anchor() references, position-try fallbacks, name registry (partial: anchor-size() & container-unit insets) |
| Scrolling | Overflow modes, scroll offset, scroll-snap, overscroll-behavior, scrollbar gutter/width/color |
| Transforms & containment | translate/rotate/scale + composed matrix, CSS contain, content-visibility: auto/hidden |
| Stacking | Stacking contexts, z-index, isolation, real top layer |
| Tables & multi-column | Positional table layout & greedy column packing (partial: colspan/rowspan, caption, true fragmentation) |
Deferred features are centralized in one LayoutWarnOnceKey enum (one warning per entity/session), so over-claiming is mechanically visible rather than silent.
Decomposed, token-driven paint components — Background, Border (elliptical per-corner radii), BoxShadow, Opacity, Outline, Filter / BackdropFilter, MixBlendMode, CssVisibility, TextColor — over WGSL shaders (quad, shadow, glyph coverage, band/gradient, effect composite). Persistent per-frame instance buffers re-upload only what changed, and a two-tier Full/Patch keyed partial re-extract narrows a single interactive value change down to re-resolving and re-uploading exactly one instance; clipping chains ancestor clip rects with radius; the effect compositor unions painted bounds into pow2-bucketed off-screen targets. (The per-view extract and the effect-composite draws are GPU-deferred behind #[ignore] adapter tests.)
One shared cosmic_text::FontSystem drives shaping → TextBuffer → ComputedTextLayout, with a glyph producer emitting coverage (alpha-as-color) instances through the shared atlas. The full editing substrate — TextEditState with ReadOnly / Disabled / SingleLine / Placeholder policy markers — sits behind a sealed facade: only one module names cosmic's editor types, enforced by a boundary tripwire test, so the engine stays swappable. Editing covers caret/selection, a platform-aware keymap, pointer drag-select, clipboard (plain text), undo/redo (1000-deep, typing-run / IME-aware coalescing), and IME composition with four documented invariants. The buiy_widgets TextInput widget (TextInput::single_line / multi_line) wraps all of this with placeholder text, click-to-focus, caret auto-scroll, and submit-on-Enter.
Buiy ships an Elm-style Model-View-Update substrate as the primary state interface in buiy_core (buiy_core::mvu). An actor declares a Model (a Clone + PartialEq + Reflect component) and its Msg; a pure free-function reducer fn(&mut M, M::Msg) -> Cmd<M::Msg> is registered via MvuAppExt (add_model / add_reducer) or the one-call App::mvu_model. Handlers only enqueue into a per-type inbox; the single ordered drain installed by MvuCorePlugin (the MvuSet::Enqueue → Drain → Bind chain) is the sole writer and commits via set_if_neq, so an idempotent fold never trips Changed<M> or cascades a re-extract (asserted by host-independent MvuWorkCounters). Reducer purity is enforced at compile time by a sealed PureEnv allowlist that excludes Commands; effects are values (Cmd::None / Emit / Batch). Three granularity tiers ship: a shared stateful-leaf reducer (one toggle_reducer for every checkbox/switch, no per-widget struct), a Model+reducer machine (MenuModel / menu_reducer), and a raw-ECS escape hatch. A record/replay tap plus the editor's command-source log, keyed by a session-stable LogicalId and merged under one global sequence, let buiy_core::replay re-fold a whole-UI session byte-identically into a fresh, same-seed app; AccessKit/agent actions fold synchronously through the same body, so AT drives become recorded, replayable messages.
MvuCorePlugin is wired automatically when you use the widgets (it powers the catalog), and is reachable for your own models via buiy_core::mvu. (partial: opt-in plugin, not folded into CorePlugin; today only the toggle leaf, the menu machine, and editor command-sourcing are wired; Cmd::task + keyed subscriptions, cross-process logs, Dialog/Popover machines, and buiy-prelude surfacing are deferred; replay is scoped to MVU-governed state — structural spawn/despawn is off-log.)
buiy_widgets ships eleven marker-driven, APG-aligned widgets, each with a bundle constructor, a mergeable bsn! scene-fn, and accessible keyboard behavior:
| Widget | Constructor | What it is |
|---|---|---|
| Button | Button::new(label) |
Focusable, accessible button; emits OnPress on pointer / keyboard / AT activation |
| TextInput | TextInput::single_line / multi_line |
Editable field — caret, selection, clipboard, undo/redo, IME; submit-on-Enter (single-line) |
| Checkbox | Checkbox::new(label) |
Tri-state checkbox; Space activates |
| Switch | Switch::new(label) |
Binary on/off toggle with sliding thumb |
| Slider | Slider::new(label, value, min, max, step) |
APG range slider; arrow / Home / End keys |
| Disclosure | Disclosure::new(label) |
Expand/collapse twisty controlling a panel |
| Dialog | Dialog::new(title, body) |
Modal dialog with focus-trap, Esc-to-close, focus restore, inert background |
| Tooltip | TooltipTrigger::new(label, tip) |
Trigger + controlled tooltip on hover / focus |
| Menu | Menu::new(items) / MenuButton::new(...) |
Roving top-layer menu with MenuItems and aria-activedescendant |
| Popover | Popover::anchored_to(anchor) |
Anchored top-layer positioning primitive (light-dismiss) |
| ScrollArea | scene-fn scroll_area(label) |
Scroll container with container-owned keyboard scrolling |
Plus general composites in buiy_widgets::composites (progress meter, table rows, search input, kbd chip, status dot). The buiy_gallery example hosts all of these across five screens in one IDE-style shell.
- AccessKit producer — decomposed,
#[non_exhaustive]a11y components rebuilt each frame into a snapshot-testable tree, pushed to bevy_winit's per-window adapters. (partial: ~21 roles today; ACCNAME is the literal-string fast path.) - Focus — ordered Tab / Shift-Tab traversal with
:focus-visible. (partial: keyboard-only, no traps / roving-tabindex yet.) - Picking — stacking-aware AABB hit-testing behind a
bevy_pickingbackend feeding aHoveredresource; the pick-set equals the paint-set (hidden nodes never absorb clicks). - Theme —
Themetoken maps (color / space / radius), a WCAG-aimeddefault_light_theme()and adefault_dark_theme()with a live accent ramp,UserPreferences(dark / reduced-motion / forced-colors / …), and a forced-colors theme covering all 16 CSS system colors.
Add Buiy alongside Bevy, then add BuiyPlugin after DefaultPlugins:
[dependencies]
bevy = "0.19"
buiy = { git = "https://github.com/intendednull/buiy" }use bevy::prelude::*;
use buiy::*;
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_plugins(BuiyPlugin) // composes layout, render, text + editing, a11y, focus, picking, widgets, and the MVU state funnel
.add_systems(Startup, setup)
.add_systems(Update, on_press)
.run();
}
fn setup(mut commands: Commands) {
commands.spawn(Camera2d);
// A focusable, accessible button (A11yRole::Button + A11yLabel wired for you).
commands.spawn(Button::new("Save"));
// An editable single-line text field — caret, selection, clipboard, IME.
commands.spawn(TextInput::single_line("Search…"));
// Themed, shaped text through the cosmic-text pipeline.
commands.spawn((
Node,
Style::default(),
Text("Hello, Buiy!".into()),
FontSize(24.0),
));
}
fn on_press(mut presses: MessageReader<OnPress>) {
for OnPress(entity) in presses.read() {
info!("button pressed: {entity:?}");
}
}Button::new returns a ready bundle — marker + Node + Style + Background / Border + Focusable + A11yRole::Button + A11yLabel — and emits OnPress (a Bevy Message) on click. The fluent Style builder (.flex_row(), .grid(), .width_px(), .padding(), .absolute(), …) is the authoring front-end to the decomposed layout components, and use buiy::prelude::*; also brings the bsn! scene macro into scope for declarative authoring.
Eight examples live under examples/. The native demos run with cargo run -p <name>:
cargo run -p hello_button # an MVU counter — interactive state routed through the MVU funnel
cargo run -p hello_text # a themed title above a wrapped body paragraph
cargo run -p hello_bsn # the same tree authored declaratively with the bsn! macro
cargo run -p todomvc # a full TodoMVC — the whole list modeled in one MVU model
cargo run -p buiy_gallery # the full widget gallery: an IDE-style shell hosting all five screenshello_text exercises the full shaping → atlas → coverage-draw path:
The hero image at the top is the buiy_gallery shell rendered headlessly; the demo asset here is produced by Buiy's own render pipeline. Regenerate the primitive demo assets with cargo run -p capture --release (offscreen render-to-texture + GPU readback, no window — see examples/capture/), and the gallery hero with CAPTURE_SHELL_SCREEN=showcase CAPTURE_SHELL_OUT=docs/assets/gallery.png cargo run -p buiy_gallery --bin capture_shell --release. Both need a real wgpu adapter and are run on demand, not in CI.
Two more examples target the browser (WebGPU + the WebGL2 reach build), built with trunk, not cargo run:
trunk serve examples/buiy_web/index.html # smallest browser target: one button on a canvas
trunk serve examples/gallery_web/index.html # the full five-screen gallery in the browserLive demo. The gallery is deployed to GitHub Pages at https://intendednull.github.io/buiy/ by .github/workflows/pages.yml — WebGPU where the browser supports it, falling back to the WebGL2 build everywhere else. To reproduce the deployable bundle locally (both artifacts plus the navigator.gpu feature-detect loader, into dist-web/), run RELEASE=1 tools/build-web.sh examples/gallery_web and serve examples/gallery_web/dist-web/.
-
Rust: stable, edition 2024. The MSRV (1.95, the floor bevy 0.19 requires) is declared by
rust-versionin the workspace manifest and enforced by a dedicated CImsrvjob;rust-toolchain.tomlpins the floatingstablechannel and therustfmt/clippycomponents. -
Bevy: 0.19.
-
Linux system deps (Bevy needs these to open a window; CI also installs
at-spi2-corefor the AccessKit AT-SPI bridge andxvfbfor headless test runs):sudo apt-get install -y libasound2-dev libudev-dev libwayland-dev libxkbcommon-dev at-spi2-core
On macOS / Windows no extra system packages are needed.
Buiy deliberately does not build on bevy_ui. bevy_ui's renderer caps capabilities Buiy treats as table stakes — non-rectangular / radius clipping, off-screen effect-group compositing, mix-blend-mode, isolation, backdrop-filter, a true top layer — so Buiy owns its renderer to reach web parity. The same logic drives the rest of the design:
- AccessKit-first, decomposed components. Accessibility ships in the foundation, not a later milestone — small public-fielded
#[non_exhaustive]components (the lesson of Bevy'sAccessibilityNodemegacomponent, bevy#17644) producing a real per-window AccessKit tree. - A swappable text substrate. The entire cosmic-text dependency is sealed inside
text::editbehind a tripwire-enforced facade. - One recordable state funnel. Widget state flows through the MVU drain, so the same surface buys record/replay, agent-drive, and a hot-reload foundation — not an afterthought bolted onto imperative mutation.
- Forced-colors as a verified path. Uniform token-only paint lets a build-time analyzer prove every default-catalog paint survives high-contrast with non-shadow state cues.
Buiy and bevy_ui can coexist per-window; they are not mixable within one UI tree.
crates/
├── buiy/ — public umbrella crate (the one you depend on); re-exports + BuiyPlugin / BuiyHeadlessPlugin + buiy::bsn
├── buiy_core/ — components, plugins, layout, render, text + editing, a11y, focus, picking, theme, animation, and the MVU state substrate
├── buiy_widgets/ — widget implementations (Button, TextInput, Checkbox, Switch, Slider, Disclosure, Dialog, Tooltip, Popover, Menu, ScrollArea) + composites
├── buiy_bsn/ — BSN authoring surface: thin re-exports of Bevy 0.19's bsn! / bsn_list! macros + spawn ext traits
├── buiy_verify/ — verification harness: visual goldens, AccessKit snapshots, contrast linter
└── buiy_bench_support/ — dev-only shared extract harness + scenes for perf measurement (never in the production graph)
examples/
├── hello_button/ — an MVU counter (state routed through the MVU funnel)
├── hello_text/ — themed shaped-text scene
├── hello_bsn/ — the same tree authored via the bsn! macro
├── todomvc/ — a full TodoMVC (the whole list modeled in one MVU model)
├── buiy_gallery/ — native widget gallery (IDE-style shell, five catalog screens)
├── gallery_web/ — the gallery in the browser (WebGPU), shares GalleryPlugin
├── buiy_web/ — minimal WebGPU browser target (single button on a canvas)
└── capture/ — headless screenshot generator for README assets
docs/ — design specs, migration plans, reports, and prior-art (start at docs/README.md)
The "run all checks" command (mirrors CI; --locked matches CI, which commits Cargo.lock and runs every cargo step locked):
cargo fmt --all -- --check && \
cargo clippy --workspace --all-targets --locked -- -D warnings && \
RUSTDOCFLAGS="-D warnings" cargo doc --workspace --no-deps --locked && \
xvfb-run -a cargo test --workspace --lockedDrop the xvfb-run -a prefix on macOS / Windows. The GPU draw paths are #[ignore]-gated and need a real wgpu adapter; the lane has two legs, both of which CI runs on a GPU host:
cargo test -p buiy_core -j 2 -- --ignored --test-threads=1
cargo test -p buiy_verify -j 2 -- --ignored --test-threads=1Before bumping any dependency, run the supply-chain check: cargo deny check.
Architecture specs, migration plans, audit reports, and a deep prior-art corpus live under docs/. Good entry points:
- Getting-started guide — build your first Buiy UI: plugin → widgets → styling → input → theming →
bsn!→ MVU state. docs/README.md— master index of every spec, plan, report, and prior-art folder.- Foundation design — the target shape: feature inventory, architecture, and sub-spec roadmap.
- MVU-as-core design — how widget state is routed, recorded, and replayed.
- Layout design · Render pipeline · Text rendering.
See CONTRIBUTING.md for the build/test commands, the GPU lane, and the
PR workflow. Buiy is experimental and largely unreviewed — see the note at the top.
Dual-licensed under either of MIT or Apache-2.0, at your option.

