macOS / Rust / Accessibility
AXUIElement in Rust on macOS: which docs.rs crate you actually want
If you searched docs.rs for an “accessibility crate” to reach AXUIElement and came back confused, that is the correct reaction. There is no single crate. There is a small stack of them at three different layers, and picking the wrong layer is how people end up writing raw FFI they did not need.
Direct answer · verified 2026-06-22
The crate most people mean by “the accessibility crate on docs.rs” is accessibility v0.2.0 , a safe wrapper over the raw FFI crate accessibility-sys v0.2.0. Import accessibility for normal code, drop to accessibility-sys only for functions the safe layer does not re-export. A newer, actively-maintained option is axuielement v0.9.1 (released 2026-06-06). All are macOS-only and checked against crates.io on 2026-06-22.
[dependencies]
accessibility = "0.2" # safe wrapper: AXUIElement, AXAttribute, TreeWalker
accessibility-sys = "0.2" # raw FFI, only if you need a lower-level callThe five crates, and the layer each one lives at
Searching docs.rs for AXUIElement surfaces results that look interchangeable but are not. Two are safe wrappers, one is the raw FFI underneath them, one is a feature inside a giant framework-bindings project, and one only handles permissions. Versions and download totals below were pulled from the crates.io API on 2026-06-22.
| Crate | Ver | Downloads | Layer | When to reach for it |
|---|---|---|---|---|
| accessibility | 0.2.0 | 45,705 | Safe wrapper | Default. Owned AXUIElement, Error enum, TreeWalker, ElementFinder. |
| accessibility-sys | 0.2.0 | 108,041 | Raw FFI (-sys) | Direct C bindings. Import when the safe layer omits a function. |
| axuielement | 0.9.1 | 301 | Safe wrapper (newer) | Actively maintained alternative, richer surface, macOS only. |
| objc2-application-services | 0.3.2 | objc2 family | Framework bindings | AXUIElement behind a feature. Pick if already on objc2. |
| macos-accessibility-client | 0.0.2 | 372,309 | Permission helper | Wraps the trust check / prompt, not element driving. |
Note the inversion: accessibility-sys (108,041) has more downloads than the safe accessibility wrapper (45,705) because crates that build their own abstractions pull the raw FFI directly. A high download count here signals “widely depended on,” not “the one you should import.”
Why the safe crate wraps the -sys crate
Apple ships AXUIElement as a C API inside the ApplicationServices framework. The Rust convention for a C API is two crates: a -sys crate that is a mechanical translation of the headers, and a safe crate that wraps it in ownership-aware types. That is exactly the split here, both published from github.com/eiz/accessibility under MIT/Apache-2.0.
The raw entry point you will see in accessibility-sys is a one-to-one mirror of Apple’s header:
// straight from the ApplicationServices header
pub unsafe extern "C" fn AXUIElementCreateApplication(
pid: pid_t,
) -> AXUIElementRef;
pub unsafe extern "C" fn AXUIElementCopyAttributeValue(
element: AXUIElementRef,
attribute: CFStringRef,
value: *mut CFTypeRef,
) -> AXError;Every call is unsafe, every return is a raw pointer or an integer error code, and you are responsible for Core Foundation retain and release. The safe accessibility crate exists so you do not write that:
use accessibility::{AXUIElement, AXAttribute};
// owned element, no manual retain/release
let app = AXUIElement::application(pid);
// AXError integers become a Rust Result
let title = app.attribute(&AXAttribute::title())?;Same underlying C functions, but the safe layer gives you an owned AXUIElement that drops correctly and an Error enum instead of stray -25204 integers. The cost is a wrapper that ships almost no doc comments, which is why the docs.rs page reads like a list of signatures with no explanation.
From cargo add to a live element in four steps
The minimum path to reading a real attribute off a running app, assuming you went with the safe crate.
Add the crate
cargo add accessibility. It will pull accessibility-sys, core-foundation, cocoa, objc, and thiserror as transitive dependencies. You do not list those yourself.
Grant Accessibility permission
Add your binary (or your terminal, while developing) to System Settings > Privacy & Security > Accessibility. Without it, the constructors still succeed but the first attribute read returns kAXErrorAPIDisabled and every tree comes back empty.
Get an element by PID
AXUIElement::application(pid) scopes you to one process and is the fast path. AXUIElement::system_wide() is the broad entry point when you need the focused app or the menu bar across app boundaries.
Read attributes, then walk children
Read AXRole / AXTitle with element.attribute(&AXAttribute::role()). To descend, remember that an application element exposes windows via AXWindows and AXMainWindow, not as ordinary AXChildren, so windows() and main_window() come before children().
Which crate, given what you are building
Pick by project shape
- Driving a few apps, want the smallest focused dependency: accessibility (safe) + accessibility-sys (transitive). Most automation code lands here.
- Already building on the objc2 ecosystem and want one binding generator for every Apple framework: objc2-application-services with the AXUIElement feature.
- Want an actively-maintained safe crate with a broader API and recent releases: axuielement v0.9.1.
- You only need to check or prompt for the trust grant, not drive elements: macos-accessibility-client.
- You hit a C function the safe layer never re-exported (a specific AXValue conversion, AXUIElementSetAttributeValue): drop to accessibility-sys for that one call and stay in the safe crate everywhere else.
Field notes from shipping against the accessibility crate
Terminator is a Playwright-shaped desktop automation framework. Its shipping platform today is Windows via UI Automation, but its macOS layer was built directly on the eiz accessibility crate before that implementation was removed from the repo on 2025-12-16. Three things the docs.rs page will not tell you, learned the hard way:
AXUIElement is not Hash or Eq
The macOS AX graph has cycles (a child points back at its parent), so a naive walk loops forever. You cannot drop AXUIElement into a HashSet to deduplicate visited nodes, because it implements neither trait. The fix is a wrapper that hashes and compares through Core Foundation’s CFHash and CFEqual on the underlying CFTypeRef.
The default TreeWalker skips windows
The crate’s TreeWalker descends through AXChildren. An application element does not list its windows as children, so a walk from the app root hits an immediate dead end. You read AXWindows and AXMainWindow explicitly, then walk children inside each window.
Send + Sync is on you
To use AXUIElement from an async runtime, Terminator wrapped it in a type with a manual unsafe impl Send + Sync, relying on the underlying Core Foundation objects managing their own thread safety. That is a deliberate, documented unsafe decision, not something the crate hands you.
One more thing the crate cannot do
Every crate here is macOS-only. The moment you need the same automation to run on Windows, AXUIElement gives you nothing; Windows uses UI Automation, a different API with a different element model. If you are building something that has to drive both, you are signing up to maintain two adapters behind one interface. That portability gap is the whole reason Terminator exists: a Playwright-style API over the OS, with the AX-versus-UIA difference absorbed under the selector layer so your script does not care which one is underneath.
Building cross-platform desktop automation and hitting the AX-versus-UIA wall?
Talk through whether to wrap the accessibility crate yourself or build on a framework that already absorbed both sides.
AXUIElement on macOS in Rust: common questions
Which Rust crate do I import for AXUIElement on macOS?
For most code you want `accessibility` (v0.2.0 on docs.rs), the safe high-level wrapper. It re-exports the AXUIElement struct, AXAttribute, the action and attribute modules, plus a TreeWalker and ElementFinder. It pulls in `accessibility-sys` (v0.2.0), the raw C FFI bindings, as a dependency. You only import `accessibility-sys` directly when you need a function the safe layer does not re-expose, like a specific AXValue conversion or a raw AXUIElementSetAttributeValue call. If you want an actively-maintained alternative with a richer API surface, look at `axuielement` (v0.9.1, released 2026-06-06).
What is the difference between `accessibility` and `accessibility-sys`?
`accessibility-sys` is the -sys crate: raw `unsafe extern "C"` declarations that match Apple's ApplicationServices headers one to one. It gives you AXUIElementCreateApplication(pid: pid_t) -> AXUIElementRef, AXUIElementCreateSystemWide(), AXUIElementCopyAttributeValue(...), the kAXErrorXXX codes, and the kAX...Attribute string constants, nothing more. `accessibility` is the safe layer on top: it wraps AXUIElementRef in an owned AXUIElement type that handles Core Foundation retain and release, turns AXError integers into a Rust `Error` enum, and adds traversal helpers. Both are published from the same repository (github.com/eiz/accessibility) under MIT/Apache-2.0.
Why does the `accessibility` crate show 0% documented on docs.rs?
Because the crate ships almost no doc comments, so docs.rs renders the type and function signatures but no prose. That is the single biggest reason people land on a search engine looking for it: the docs.rs page tells you AXUIElement::system_wide() exists and returns Self, but not that constructing it is infallible while reading any attribute off it requires accessibility permission, or that its default TreeWalker does not descend into application windows. The signatures are accurate; the behavior lives in the source and in tools that have shipped against it.
Is `accessibility-sys` still maintained in 2026?
It is stable rather than abandoned. v0.2.0 was published 2025-03-22 and is roughly 562 lines across the crate. Apple's AXUIElement C API has not changed in years, so the bindings do not need frequent updates; the -sys crate tracks a stable system framework. It carries 108,041 total downloads (verified on crates.io 2026-06-22), which is more than the safe `accessibility` wrapper above it, because other crates depend on the raw layer directly.
What about objc2-application-services? Should I use that instead?
objc2-application-services (v0.3.2, part of madsmtm's objc2 project) exposes the ApplicationServices framework, and AXUIElement is available behind a cargo feature. It is the right choice if your project is already built on the objc2 ecosystem and you want one consistent binding generator across every Apple framework you touch. If you are not already in objc2 land, the eiz `accessibility` crate is a smaller, more focused dependency that does only AX. Both reach the same underlying C functions.
Do I need accessibility permission to call these crates?
Yes, for reading. Creating an AXUIElement (system_wide, application(pid)) does not prompt or fail, but the first AXUIElementCopyAttributeValue call from an untrusted process returns kAXErrorAPIDisabled and you get empty trees. The calling process must be granted Accessibility in System Settings > Privacy & Security > Accessibility. The thin `macos-accessibility-client` crate (v0.0.2) exists specifically to wrap the trust-check and prompt side of this (AXIsProcessTrusted / the prompt option dictionary), separate from the element-driving crates.
Does Terminator use the `accessibility` crate?
Terminator is a Playwright-style desktop automation framework whose shipping platform today is Windows via UI Automation. Its macOS layer was built on the eiz `accessibility` crate's AXUIElement, and that worked example is what this guide draws from: a windows-aware TreeWalker, a CFEqual/CFHash cycle guard for the system-wide tree, and a manual `unsafe impl Send + Sync` wrapper so AXUIElement could cross threads in an async runtime. The macOS implementation was removed from the repo on 2025-12-16, so the code is historical, but the crate-selection lessons stand.
Can I drive Windows apps with these crates too?
No. AXUIElement and every crate on this page are macOS-only; they bind Apple's Accessibility C API and will not compile or link on Windows. On Windows the equivalent layer is UI Automation (UIA), a completely different API with its own element model. A cross-platform tool keeps two adapters behind one selector interface. That portability gap, AX on macOS versus UIA on Windows, is exactly what a framework like Terminator absorbs for you.
Keep reading
accessibility crate: AXUIElement::system_wide(), and what the docs leave out
The system-wide root constructor, the three AXUIElement constructors, and the two traversal traps docs.rs never mentions.
macOS accessibility UI tree automation: the write path nobody warns you about
AXPress and AXClick return success and do nothing on browser views. The 3-tier click fallback a real AX engine ships.
AXObserver in the eiz accessibility crate
Subscribing to AX notifications, run loop sources, and the lifecycle gotchas around observer callbacks.
Comments (••)
Leave a comment to see what others are saying.Public and anonymous. No signup.