accesskit
A cross-platform accessibility tree schema and action trait for UI toolkits, written in Rust.
Repository Health
Technical Analysis
AccessKit is the core crate of the AccessKit project: a data schema that describes everything a screen reader or other assistive technology needs to understand a UI, plus the trait toolkits implement to handle incoming action requests such as focus, click, or text selection. Rather than each UI toolkit reverse-engineering Windows UI Automation, macOS NSAccessibility, AT-SPI, and Android’s accessibility API separately, toolkit authors build one AccessKit tree and hand it to a platform adapter crate that translates it into the native API.
The schema is modeled closely on Chromium’s internal cross-platform accessibility abstraction: nodes carry a Role, a bag of optional properties, and a set of supported Actions, and updates are pushed incrementally via TreeUpdate rather than pulled on demand. This crate is no_std-compatible by default (falling back to alloc), keeps zero required third-party runtime dependencies beyond uuid, and gates serde/schemars/PyO3 support behind Cargo features so consumers only pay for what they use.
What You Get
- A
Roleenum covering the ARIA role vocabulary (button, text input, tree item, alert, and dozens more), ordered for compact serialization - A
Nodetype with property getter/setter/clear macros for labels, bounds, states, relationships, and text runs - An
Actionenum plusActionRequest/ActionHandlertrait for receiving focus, click, scroll, and text-editing requests from assistive technologies TreeUpdate, the incremental-update structure adapters consume to build and mutate the live accessibility tree, including sub-tree/graft-node support for multi-process or multi-actor UIs- A
geometrymodule (Affine,Point,Rect,Size,Vec2) adapted from kurbo for expressing node bounds and transforms - Optional
serdeandschemarsfeatures for serializing the tree and generating a JSON Schema, and an optionalpyo3feature for the official Python bindings
Common Use Cases
- Immediate-mode or custom-rendered GUI toolkits (e.g. egui-style libraries) exposing their widgets to screen readers without hand-rolling each platform’s accessibility API
- Game engines and canvas-based applications adding assistive-technology support to a UI that doesn’t use native platform widgets
- Cross-platform Rust application frameworks wiring up
accesskit_winit(or another adapter) to get accessibility across desktop targets from one tree definition - Non-Rust toolkits consuming the C or Python bindings to get the same schema and adapters without writing Rust directly
Under The Hood
Architecture
accesskit’s design mirrors Chromium’s browser/renderer split: the crate defines a TreeUpdate push model where the producer (a UI toolkit) incrementally submits (NodeId, Node) pairs plus tree/focus metadata, and a downstream accesskit_consumer::Tree (in the sibling crate) retains the full tree and diffs updates against it. Node itself is built from macro-generated property accessors (impl_node_id_property_methods!-style patterns around line 1300+ of lib.rs) backed by a Properties/PropertyIndices struct that trades a flat, indexed representation for compact storage rather than one field per possible attribute. Action, Role, and the various attribute enums (Invalid, Live, AriaCurrent, etc.) are plain #[repr(u8)] enums so they serialize cheaply and map directly onto platform API constants in the adapter crates. The sub-tree/graft-node mechanism (documented in ARCHITECTURE.md) lets independent actors — for example separate processes or components — each own a NodeId namespace that gets stitched together by the adapter, without requiring coordination between producers.
Tech Stack
The crate is Rust, no_std by default (#![cfg_attr(not(any(feature = "pyo3", feature = "schemars")), no_std)]) with alloc for Box/String/Vec, so it can run in constrained embedded-style environments as well as full applications. Its only unconditional dependency is uuid (for TreeId); serde/serde_json and schemars are opt-in for serialization and JSON Schema generation, enumn backs numeric round-tripping for enums under the serde feature, and pyo3 is opt-in for the official Python bindings. It lives inside a Cargo workspace alongside accesskit_consumer and platform adapters for Android, iOS, macOS, Unix (AT-SPI via zbus), Windows (UI Automation), and winit, all pinned to a shared rust-version = "1.85" and 2024 edition. CI runs cargo fmt, cargo deny across eight target triples, and clippy/tests per platform, so the workspace enforces cross-target buildability on every change.
Code Quality
Testing is extensive and inline: lib.rs alone (3,700 lines) carries dozens of #[cfg(test)] mod tests blocks generated by the same macros that produce the property accessors, so every generated getter/setter/clearer pattern gets a matching round-trip test rather than relying on hand-written coverage. geometry.rs (866 lines, ported from kurbo) follows the same pattern for its Affine/Point/Rect/Vec2 math. There is no unsafe code in either file. The project enforces cargo fmt and clippy in CI across every supported OS/adapter combination, plus cargo-deny license/advisory checks across eight target triples, giving strong guardrails against regressions in a codebase with a large enum/macro surface area.
API Design
The public API leans on Rust’s type system to make invalid accessibility states hard to construct: roles, actions, and node states are closed enums rather than strings, and Node’s many optional properties are exposed through consistent getter/setter/clearer method triples generated from a handful of macros, so the ergonomics stay uniform across dozens of properties instead of drifting field by field. The tradeoff is a steep initial learning curve — consumers need to understand the TreeUpdate push model and sub-tree/graft-node concepts before they can wire up a working tree — but ARCHITECTURE.md and the README-APPLICATION-DEVELOPERS.md document exist specifically to front-load that context, and the crate itself ships no CLI or scaffolding, keeping its surface limited to the schema and action trait it’s meant to own.