About the two scanning models: static source vs live DOM
b8e looks for accessibility defects in two fundamentally different ways, and the shape of the
command you run decides which one you get. b8e scan . reads your source files and never opens
a browser. b8e scan --url, --crawl, and --story drive a real browser and inspect the
rendered DOM. The two lanes answer different questions, and understanding why they are separate
explains a lot about where each one is trustworthy and where it is blind.
Two questions, not one tool run twice
Section titled “Two questions, not one tool run twice”A static source scan asks: what does the code say? It parses your components — the JSX, the
markup, the framework templates — and reasons about the accessibility contract each element
declares at rest. An <img> with no alt, a control with no accessible name, a heading level that
skips: these are properties of the source, visible without ever running anything.
A live-DOM scan asks a different question: what does the page actually become? It launches a browser, navigates, lets the framework hydrate and the JavaScript run, and only then reads the accessibility tree the user’s assistive technology would see. That tree is the product of routing, data fetching, client-side rendering, portals, and conditional branches — none of which the source alone can settle.
These are not two views of the same fact. They are two facts. A defect can be plainly wrong in the
source and disappear at runtime (a value injected by a wrapper), or be invisible in the source and
appear only once the page renders (a modal that traps focus, a live region that never announces).
Neither lane is a superset of the other, which is the core reason b8e keeps them apart rather than
pretending one command covers everything.
Why the source lane is deterministic
Section titled “Why the source lane is deterministic”The static scan’s defining property is determinism: the same source produces the same findings, every time, with no network, no browser, and no flakiness. That is a deliberate design choice, not an accident of implementation. A source scan is fast enough to run on every commit, reproducible enough to gate a pull request, and honest enough that a green result means this input is clean rather than this run happened to pass.
Determinism also sets the boundary of what the lane can claim. Reasoning about source means reasoning about code the scanner can read and resolve. When a component’s real behavior lives behind a boundary the source can’t cross — a value computed at runtime, a wrapper whose host it can’t trace — the honest move is to report the element as opaque rather than guess. That instinct, honesty over coverage, runs through the whole static lane; it is discussed further in About component-to-host resolution.
Why the DOM lanes exist at all
Section titled “Why the DOM lanes exist at all”If the source lane is deterministic and cheap, why keep a slower, browser-driven one? Because a large class of real accessibility failures simply do not exist until the page runs. Focus order, dynamic ARIA state, announcements from live regions, contrast against actually-painted pixels, the behavior of a route after client navigation — all of these are runtime properties. No amount of source reading recovers them, because the source is not where they happen.
The --url, --crawl, and --story flags are three ways into that same live-DOM lane, differing
only in what gets rendered:
--urlrenders one or more specific pages and aggregates them into a single site report.--crawltakes one seed URL and expands it into a same-origin page set before aggregating, for when you want breadth without enumerating every route by hand.--storyrenders Storybook stories, which is often the most useful shape during development: components exercised in isolation, in the states their authors actually care about.
The trade-off is the mirror image of the static lane’s. Live-DOM findings are richer and closer to
the user’s real experience, but they depend on a browser, a network, and a page that renders
predictably — so they cost more and carry the ordinary fragility of anything that drives a real
browser. For the exact flags, budgets, and defaults, see the
b8e scan reference.
Where audit fits, and why it is a third thing
Section titled “Where audit fits, and why it is a third thing”It is worth naming a third path so it is not confused with either scan lane. b8e audit runs
outcome-based journeys through a real browser and, in its cli client-type, a real screen reader —
it verifies whether a user can complete a task, not just whether the markup is well-formed. That
is a different unit of truth again: a scan reports defects on a surface, an audit reports whether a
journey succeeds. The b8e audit reference covers its options; the
distinction matters here only to make the point that “does the code declare the right thing”, “does
the rendered page expose the right thing”, and “can a person actually get through the flow” are
three separable claims, and b8e gives each its own command rather than blurring them.
The takeaway
Section titled “The takeaway”Reach for the static lane when you want a fast, reproducible gate on what the code says — the loop the scan-and-fix tutorial walks through. Reach for a DOM lane when the question is about what the page becomes at runtime, which the source can never fully answer. The separation is not an incompleteness to be papered over; it is the honest shape of the problem, and each lane is trustworthy precisely because it does not pretend to cover the other’s ground.