Part 4. The component contract

AFDS user guide

What a component declares about itself

This section is the part of the guide a developer will keep open while working. It covers Part II of the specification, clauses 7 to 19, which is the core of the format: the record every component carries, whatever design opinions its authors hold.

Part II is normative in full, and it applies to every component, layout primitive and pattern in a package, whatever method profile the package claims (Part II preamble; clause 4.3). The method profiles of Part III are a separate axis and are covered later in this guide; where a rule below belongs to a profile rather than to the core, it is labelled as such and cross-referenced rather than restated.

One reminder about reading the specification itself. The capitalised keywords in it carry the force set out in clause 4.1, and a lower-case "must" or "should" in an informative clause carries no requirement at all. This guide is informative, so it does not issue requirements in its own voice. Where it says that something is required, the clause named beside it is the requirement, and the specification is what a reviewer will hold you to.

The division Part II draws is worth stating once, because it is the one readers most often get wrong. Recording which native element you considered and why it was insufficient is a disclosure obligation, and it sits in the core. Preferring the native element is a design rule, and it sits in Part III, where it binds only a package that asks for it (Part II preamble; clause 8.3). An organisation can therefore be held to complete disclosure without being held to this project's taste.

Declaring a component

Who this serves: developers, and testers reading a component to work out what to test.

A component specification is the machine-readable record of what a component is, what it promises, what it refuses to promise, and how each of those statements can be checked (clause 7.1).

Three rules govern the artefact itself before any field is filled in. Every component, layout primitive and pattern in a package has exactly one canonical specification; the specification is a JSON document; and where any other artefact in the package states the same fact, the specification governs and the other artefact is derivative (clause 7.1).

A fourth rule changes how you work: a specification cannot be generated from an implementation by inspection alone (clause 7.1). A specification derived from code can only ever record what the code does, and the purpose of the document is to record what the component is obliged to do, so that the two can be compared and found to differ. A generator that reads the DOM and writes the contract makes that comparison impossible by construction.

The seventeen required fields

The old guide gave ten fields and scoped them to derived components. Both halves of that were wrong. Clause 7.2 requires seventeen fields in every component specification, derived or not.

FieldWhat it carriesRequiredClause
afdsSpecVersionThe version of the AFDS specification this document conforms toRequired7.3
idStable, unique identifier for the component within the packageRequired7.3
nameThe human-readable nameRequired7.3
kindlayout-primitive, component, or patternRequired7.4
versionThe component's own version, under the Part IV payload rulesRequired7.3
statusdraft, proposed, stable, deprecated, or withdrawnRequired7.4
summaryWhat the component does, in prose, in no more than a short paragraphRequired7.3
semanticModelRole, native element, accessible-name source, rationale, reading order, consumer obligationsRequired8
derivationThe derivation status, and for a derived component the pattern fieldsRequired9
keyboardContractThe eight stages, or an explicit declaration that there is no contractRequired10
reflowBehaviourThe seven reflow disclosures, including any two-dimensional exception claimRequired11
wcagMappingOne entry per success criterion the component bears onRequired12
guaranteesThe commitments the component makes, each naming its testsRequired14
nonGuaranteesWhat the component explicitly does not commit toRequired14
assertionsThe testable statements, automated and manual, with proceduresRequired15
uncertaintyWhat is not known, with a status from the closed vocabularyRequired17
testsFixture locations, isolated and realistic-pageRequired18

Nothing in that list is optional. A producer cannot omit a field on the grounds that it does not apply; where a field does not apply, the specification says so explicitly in the form the relevant clause defines (clause 7.2).

Clause 7.2 calls this the most important structural rule in Part II, and gives the reason plainly. An omitted field and an inapplicable field look identical to a reader, and the reader will resolve the ambiguity in the direction that flatters the component. So a component with no keyboard contract records that it has none, in words, so that a reviewer cannot mistake absence for oversight (clauses 7.2, 10.3).

Four fields carry extra obligations for a derived component, additional to the seventeen rather than a replacement for them, and they are covered under derivation below (clause 9.3). Keep the counts apart: seventeen machine-readable fields in clause 7.2, four extra derivation fields in clause 9.3, twelve engineering review items in clause 24.4, and eleven design-handoff annotation fields in clause 19.1. A count of one is never a count of another (clauses 19.1, 24.4).

Identifiers

afdsSpecVersion records the version of the specification the document conforms to (clause 7.3).

id is stable for the life of the component and unique within the package, and an id is never reused for a different component after the original is withdrawn (clause 7.3). This is the field that makes withdrawal detectable. If you recycle dialog for a different component two years later, every consumer holding evidence against the old dialog now holds evidence about something else, and nothing in the package says so.

name is for humans, and summary states what the component does and should also state what it does not do (clause 7.3). version follows the payload versioning rules in Part IV, and is independent of both the package version and afdsSpecVersion (clause 7.3).

Kind and status

kind takes one of three values, and these are the same three layers the guide described earlier (clause 7.4).

ValueMeaning
layout-primitiveA composable arrangement rule that positions content without knowing what it means
componentAn interactive or structural element with declared semantics
patternSeveral components co-operating through a task

status takes one of five values (clause 7.4).

ValueMeaning
draftUnder development. Nothing in it can be relied on to remain stable.
proposedComplete and awaiting review. Stable in shape, not yet in content.
stableReviewed, and subject to the Part IV versioning rules.
deprecatedStill present and still supported, with a replacement named.
withdrawnNo longer supported. Present so that consumers can detect the withdrawal.

A deprecated or withdrawn specification states the reason and, where one exists, the replacement id (clause 7.4). That is why withdrawn components stay in the package instead of disappearing from it: a component that vanishes teaches a consumer nothing, while one marked withdrawn with a reason and a successor tells them what to do next.

Component documentation

Every component specification should have component documentation: a human-readable counterpart carrying the reasoning the JSON cannot express (clause 7.5). The counterpart does not contradict the specification, and where the two disagree the specification governs and the disagreement is a defect in the package rather than a matter for interpretation (clause 7.5).

Component documentation exists because a component specification records decisions without recording why they were taken, and a decision whose reasoning is lost cannot be safely revisited (clause 7.5).

The semantic model

Who this serves: developers and designers together. This is the field where a design decision becomes a machine-readable claim.

The commonest accessibility question about any component is the least glamorous one: what is this thing called, what state is it in, and what is it related to. Most failures of an otherwise correct component are failures of that answer, or of the markup around it that was supposed to supply it.

The semanticModel object records six things (clause 8.1).

KeyWhat it records
roleThe ARIA role the component exposes, or none where it exposes no role
implicitElementThe native element the component renders as its own outermost element
accessibleNameThe source of the accessible name, or none where the component has no accessible name of its own
rationaleProse explaining why the semantics are what they are
domOrderIsReadingOrderA boolean stating whether the component preserves document order as reading order
consumerObligationsAn array of statements, drafted under clause 8.2

A component that exposes no role records none rather than omitting the field, and its rationale says why no role is correct (clause 8.1). The reasoning is worth carrying in your head: a component with no semantics is making a claim, not declining to make one (clause 8.1). A layout primitive that carries geometry and no ARIA is the ordinary case here, and its rationale is that the primitive cannot know whether its children form a list, so it does not guess (clauses 8.1, 8.3, 14.4).

Consumer obligations

A consumer obligation is a statement of something the consumer has to do for the component to be used correctly (clause 8.2).

Three drafting rules apply. Every obligation is written as a requirement on the consumer rather than as a description of the component; every obligation uses the conformance language of clause 4.1; and an obligation is never used to discharge a responsibility the component could reasonably meet itself (clause 8.2). That last rule has a blunt gloss in the specification: writing an obligation is not a way of exporting difficulty (clause 8.2).

The mechanism exists because most accessibility failures involving a correct component are failures of the surrounding markup (clause 8.2). A primitive that arranges children cannot know whether those children form a list; the consumer who does know is the only party able to supply the semantics, and recording that as an obligation moves it from folklore into the contract.

The native baseline

The semanticModel also records the native baseline: the behaviour and semantics the component would have if built from platform-native elements without added roles or scripted behaviour (clause 8.3).

Where the component is not built on that baseline, the specification states which native element was considered and why it was insufficient (clause 8.3). An answer of the form that no native element was considered is a valid answer, and is recorded as such rather than left blank (clause 8.3).

Read the strength of that clause carefully. Clause 8.3 requires disclosure and does not require a preference (clause 8.3). A package that always answers this field by saying a native element was rejected for visual reasons conforms to the core, provided it says so — and its reviewers now have something to argue with, which is the point (clause 8.3). The preference for native elements is clause 24.3, and it binds only a package claiming the afds-patterns-native-first profile.

Native HTML first, and deriving from published patterns

Who this serves: developers deciding what to build, and reviewers deciding whether the decision was made or merely arrived at.

Adopted policy, not a proposal

Native HTML first is this project's adopted position, and the treatment of external pattern guidance is settled. It is recorded as a colophon decision, marked settled in the open-questions register, and carried normatively as clause 24, the native-first pattern profile, identifier afds-patterns-native-first. The project claims that profile in the packages it publishes. Anything you may have read describing it as proposed or not yet adopted is out of date.

Clause 24.1 states the position in five clauses, and each does work.

WCAG establishes the required outcome. Native HTML is preferred. ARIA fills genuine semantic gaps. A published pattern guide supplies the interaction model for recognised custom patterns. The package specifies, tests, versions, and evidences the implementation actually shipped.

Each clause does work. The first fixes the acceptance criteria in a normative standard, so a disagreement about behaviour resolves against an outcome rather than a preference. The second sets the default engineering answer, because native elements arrive with focus behaviour, activation semantics, disabled-state handling and forced-colours treatment already implemented and already tested by browser vendors. The third confines ARIA to the repair role it was designed for. The fourth admits that some interactions have no native equivalent, and that a custom one should behave the way users already expect. The fifth locates responsibility, because no external document can carry evidence about the code a package actually ships (clause 24.1). The statement is this project's own formulation and is not attributable to the W3C or to any working group (clause 24.8).

In a package claiming the profile, the preference becomes a rule: a component is not given a pattern-derived status where a native element in the clause 24.3 table would have supplied the semantics and interaction, unless the component specification records why the native element was insufficient (clause 24.3). The table is ordered from cheapest to most expensive, and the order is part of the advice.

Product needPreferred responseWhy
ActionNative <button>Activation, focus, disabled state and keyboard behaviour are already provided
Choice between optionsNative radio or checkbox inputsAvoids recreating form semantics
NavigationLinks inside a navigation landmarkDo not convert site navigation into a menu widget
Reveal supplementary contentNative <details>, or a button with controlled contentOften avoids a full custom disclosure implementation
Modal confirmationA dialog component following the published dialog modelA genuine composite interaction with focus-management needs
Rich autocompleteA combobox, only where native controls cannot satisfy the taskHigh complexity; semantics and keyboard contract must be complete
Large interactive results tableA native table first; an ARIA grid only where directional cell navigation is genuinely neededA visual CSS grid is not a semantic grid and does not justify the clause 22 exception

The rule is deliberately phrased as a restriction rather than as an endorsement, because the likeliest failure mode for a system that admires a pattern guide is to turn every familiar interaction into a custom widget (clause 24.3).

One correction to a common list of mistakes. Converting site navigation into a menubar is not a misuse of the menu and menubar pattern, and the specification says it must not be described as one (clause 24.5). The published pattern is not restricted to application menus, and its publisher ships a navigation menubar example demonstrating site navigation (clauses 24.5, 24.8). What clause 24.5 records is a convention of this profile with a stated cost: adopting a menubar for ordinary navigation imports the whole composite contract — a roving-focus model, a single tab stop, author-managed arrow-key movement, submenu behaviour, and a role that causes a screen reader to describe the thing as a menu rather than as navigation. A package claiming the profile may adopt a menubar anyway, and if it does, the justification appears in the component's specification tagged as a product-deviation under clause 13, with the keyboard contract written out in full (clause 24.5).

The five derivation statuses

Every component in a package carries exactly one derivation status, and the set of those records is the pattern registry (clause 9.1). This is a core requirement on every package, not a proposal and not a profile matter.

The derivation.status field takes one of five values (clause 9.2).

ValueMeaning
native-firstA native element fully supplies the interaction
pattern-derivedA custom component implements a recognised published pattern
pattern-adjacentA similar interaction that intentionally differs from the published pattern
customNo mature published pattern applies
prohibitedThe pattern creates more accessibility cost than value and is not to be used

Use those spellings. Earlier drafts of this project named two of them after the ARIA Authoring Practices Guide specifically — APG-derived and APG-adjacent — and they were deliberately renamed so that the core vocabulary of clause 9 does not presuppose one pattern guide (clause 24.8). Anything still using the old names is stale, and a package using them does not carry a valid derivation.status at all (clause 9.2). Throughout the core, the neutral term is "published pattern guide"; the guide this project uses is the APG, and clause 24.8 records that adoption in the profile where it belongs.

The statuses are not a quality ranking: a component is not defective for being pattern-derived, and a package whose components are mostly native-first is not thereby better. What the registry records is that the status was decided and reasoned, rather than arrived at (clause 9.2).

Two statuses carry extra obligations. A pattern-adjacent entry names the pattern it resembles and states exactly where and why it departs, so that a component is not labelled with a pattern name it does not honour (clause 9.2). A prohibited entry states the cost that motivated the prohibition, and is revisitable if the underlying support picture changes; a prohibition without a stated cost is an opinion that cannot be reviewed (clause 9.2).

What a derived component records in addition

Where derivation.status is pattern-derived or pattern-adjacent, four further things are recorded (clause 9.3).

#What is recordedNotes
1The pattern name and its source URLThe URL, and not the name alone
2The native alternative considered, and why it was insufficientAn insufficiency finding, not an open question
3Every deviation from the published pattern, each with its reason and its cost, each tagged under clause 13Reason alone is not enough
4Whether the pattern is support-dependent, and if so the reassessment trigger required by clause 9.5See below

A derived component with no deviations records that explicitly. Silence about deviations is never to be read as an absence of them (clause 9.3).

The tag on a deviation comes from the clause 13 vocabulary below; in practice most deviations are product-deviation or support-limitation, and a product-deviation carries its cost as well as its reason (clauses 9.3, 13).

Support-dependent patterns, and the reassessment trigger

A pattern is support-dependent where its declared behaviour is known to depend on assistive-technology or engine support that is incomplete (clause 9.5).

A support-dependent component records a reassessment trigger stating the condition under which its specification is reopened (clause 9.5). The trigger is required because a change in support is the main reason a settled contract silently becomes wrong, and without a trigger the change is noticed by accident, usually by a user (clause 9.5).

A usable trigger names a condition, not an intention: "reopen when the recorded partial result for JAWS with Chrome becomes supported on two consecutive JAWS releases" is a trigger; "revisit periodically" is not.

The pattern registry, core and profile

There are two things called a registry and they belong to different axes.

The core registry is the set of per-component derivation.status records, and every package carries it because every component carries a status; without it, whether a component follows a recognised interaction model is a property of whoever wrote it first, discoverable only by reading the implementation (clause 9.1).

The package-level registry artefact is a profile requirement. A package claiming afds-patterns-native-first carries a registry listing every component and pattern in the package against its status; the registry does not disagree with any component's own declaration, and where they differ the component specification governs and the package is defective (clause 24.2). That profile registry also records a prohibited entry for a pattern the package has declined, even though no component implements it (clause 24.2).

That last requirement is the reason the artefact is worth having, and it is the one thing per-component declarations cannot supply, because a decision not to build something leaves no component behind to declare it. Without a package-level registry, the absence of a menubar component looks identical to nobody having considered a menubar, and the argument gets held again in the next review (clause 24.2).

What a derivation may not claim

This is the hard limit, and it is core, binding every package.

A specification does not state or imply that a component conforms to an informative document, and in particular a package does not claim that a component conforms to the ARIA Authoring Practices Guide, "because that guide is informative and has no conformance model to conform to" (clauses 4.4, 9.4).

The reasoning is a distinction beginners are rarely told. WCAG 2.2 and WAI-ARIA are normative standards with conformance models; the APG is informative guidance. A component can follow every keystroke recommendation in a published pattern and still fail WCAG, and a component can depart from a pattern's key map and still conform to WCAG. There is nothing in an informative document for a claim to be measured against, so the sentence "this component conforms to the APG" is not a weak claim, it is not a claim at all.

What you can publish instead is fixed: the accessibility criteria met, the semantics used, and the recorded assistive-technology results (clauses 4.4, 9.4).

Two further prohibitions follow. Recording that a component is derived from a published pattern is a statement about where the interaction model came from; it is not a conformance claim, it carries no assurance, and a consumer does not treat it as evidence of anything (clause 9.4). If you are the developer consuming somebody else's package, that is aimed at you: pattern-derived tells you nothing about whether the component works. And a specification does not cite a published pattern's own example implementations as evidence for the component, because such examples are written to demonstrate a pattern legibly, which is a different goal from being production code, and no external example can carry evidence about the code a package actually ships (clause 9.4).

The keyboard contract

Who this serves: developers writing the contract, testers executing it, designers who need to know what they have implicitly specified.

The keyboard contract is the load-bearing part of a component specification, and its name understates it (clause 10.1).

"Keyboard" does not mean a keyboard

A keyboard interface is an input pathway rather than a physical device (clause 10.1).

Get the attribution right, because a reviewer who gets it wrong loses the argument. WCAG 2.2 defines a keyboard interface narrowly, as an interface used by software to obtain keystroke input. The breadth comes from what drives that interface, and it is the Understanding document for Success Criterion 2.1.1 that lists speech input software, sip-and-puff software, on-screen keyboards, scanning software, and a variety of assistive technologies and alternate keyboards among keyboard emulators (clause 10.1). A reviewer who cites the definition for the emulator list is citing the wrong document (clause 10.1).

The definition also carries an exclusion: operation through a keyboard-operated mouse emulator does not qualify, because the program is being driven through its pointing-device interface instead, and a component exercised only that way is not recorded as having been tested for keyboard operation (clause 10.1).

The consequence is the point of the whole clause. A component's keyboard contract is simultaneously its switch-access contract, its scanning contract, and much of its speech-input contract (clause 10.1). Testing with a physical keyboard is necessary, and the specification is explicit that it is not to be treated as sufficient (clause 10.1).

The eight stages

Where a component has a keyboard contract, the contract declares all eight of the following (clause 10.2). The conditional matters: it is what makes clause 10.3 coherent for components that have no contract.

StageWhat it declares
1. EntryWhat receives focus when the user moves into the component, and what happens on re-entry after leaving
2. Internal movementWhich keys move focus inside the component, whether movement wraps, and whether roving tabindex or aria-activedescendant is used
3. ActivationWhich keys act on the focused item, distinguishing keys that change selection from keys that commit an action
4. ExitWhether Tab leaves, whether Escape dismisses, and where focus goes in each case
5. State changeWhat is conveyed after expansion, selection, validation failure, loading or deletion, and by what mechanism
6. RestorationWhere focus returns when a popup or dialog closes, including when the invoking control no longer exists
7. Pointer and touch parityWhether all functionality is reachable without hover, without drag, and without a path-dependent pointer movement
8. Speech-recognition operationWhether every visible interactive control has a stable visible label, and whether visible text is contained in the accessible name

Three rules attach to particular stages. Stage 3 distinguishes selection from commitment, because conflating them is what produces accidental destructive operations (clause 10.2). Stage 6 names a documented logical successor for the case where the invoker no longer exists, which is common wherever an action deletes the row containing its own trigger (clause 10.2). And an exit path that depends on the user guessing is not recorded as satisfying stage 4, so "the user can press Escape" is a satisfactory answer only if Escape is discoverable (clause 10.2).

Note that stage 5 asks what is conveyed and by what mechanism, not what a screen reader announces. Announcement is one mechanism; a visible status region, a validation message tied by aria-describedby, or a change of accessible name are others.

The keys a contract has to answer for

The specification fixes the stages, not a key map. Key bindings are conventions, tagged recommended-by-convention under clause 13 unless a standard requires them, and a component may depart from them if it labels the departure and states its cost (clause 13). The table below is the set of bindings a contract normally has to say something about.

KeyStageWhat the contract has to state
Tab1, 4Whether it enters, whether it leaves, and where focus lands in each direction
Shift+Tab1, 4The same, backwards, including re-entry after leaving
Arrow keys (Up, Down, Left, Right)2Whether they move focus inside the component, on which axis, and whether movement wraps
Home, End2Whether they jump to the first and last item
Page Up, Page Down2Whether they move by group, where the component has groups
Type-ahead characters2Whether printable characters move focus, and how a single-character binding avoids colliding with speech-recognition and screen-reader command sets
Space3Whether it changes selection or commits, which is the distinction stage 3 requires
Enter3Whether it commits, and what it commits
Escape4Whether it dismisses, and where focus goes when it does
Modifier combinations2, 3Any custom shortcut, and whether it can be turned off or remapped

Where roving tabindex or aria-activedescendant is used, stage 2 requires the contract to say which (clause 10.2). The two produce different focus behaviour under assistive technology, and a contract that says only "arrow keys move focus" has not answered the stage.

Declaring the absence of a contract

A layout primitive that arranges boxes has no keyboard contract, and that is a normal, conforming state. Where a component has no keyboard contract, keyboardContract.hasKeyboardContract is false and the object carries a statement saying so explicitly (clause 10.3).

The statement is positive rather than empty, because a reviewer reading an empty keyboard contract cannot tell whether the component has none or whether nobody filled it in, and those are opposite findings (clause 10.3). "This primitive exposes no interactive controls of its own and receives no focus; keyboard behaviour belongs to the content the consumer places inside it" is a positive statement; an empty object is not.

The focus lifecycle

Separately from the eight stages, the contract records four booleans — whether the component receives focus, moves focus, traps focus and restores focus — together with a note explaining the combination (clause 10.4).

These four are recorded separately because they are the properties a consumer needs in order to reason about composition, and the example the specification gives is the one you will meet: a page containing two components that both trap focus has a defect that neither component's own tests can detect (clause 10.4). That is also why the booleans are worth writing even when they feel obvious from the prose — the prose is not queryable, and the composition question is asked by a tool assembling a page, not by a reader.

Four checks are worth carrying out and are not part of clause 10; they are WCAG criteria in their own right, and this guide states them as commentary rather than as contract requirements. Avoid fine pointer paths, which exclude switch and scanning users and often fail Pointer Gestures. Avoid hover-only discovery, which is unreachable to keyboard-interface users and unstable for magnifier users. Avoid drag-only movement, because reordering needs a single-pointer and keyboard-interface alternative, which is the substance of Dragging Movements. Avoid custom single-character shortcuts that cannot be turned off or remapped, because they collide with speech-recognition and screen-reader command sets. Where these bear on a component, they are recorded in the wcagMapping array under clause 12, which is where a criterion belongs.

Reflow and layout behaviour

Who this serves: developers and designers; the tester inherits the claim and has to check its boundary.

What a component records about reflow

The reflowBehaviour object records seven things (clause 11.1).

#What is recorded
1Whether the component is intrinsic, meaning that it responds to available space rather than to a chosen breakpoint
2Whether it uses layout media queries
3What author-fixed dimensions it declares, or none
4Whether it declares fixed heights
5The mechanism, in prose, by which it reflows
6Whether it operates without JavaScript
7Whether it claims the two-dimensional exception, and the rationale for that claim

Clause 11.1 is in the core rather than in a method profile because the declaration is a disclosure, not a design rule: a package whose components all use media queries and fixed heights conforms to the core, provided it says so (clause 11.1). If you want the design rules — intrinsic layout, the primitive set, the prohibition on absolute media queries — those are the layout method profile, clause 21, and they bind only a package claiming it.

The criterion

WCAG 2.2 Success Criterion 1.4.10 requires content to be presentable without loss of information or functionality and without two-dimensional scrolling, at a width equivalent to 320 CSS pixels for vertically scrolling content and a height equivalent to 256 CSS pixels for horizontally scrolling content. A width of 320 CSS pixels corresponds to a 1280 CSS pixel starting viewport at 400 per cent zoom. The criterion excepts parts of the content that require two-dimensional layout "for usage or meaning", and its cited examples include data tables, qualified as "not individual cells" (clause 22.2).

The two-dimensional exception

Where a component claims the exception, the specification gives a rationale resting on semantic two-dimensional structure (clause 11.2). This is core and binds every package.

The rationale does not rest on visual appearance, and does not rest on the layout technique used to produce the appearance; a region that merely looks like a grid does not qualify (clause 11.2). Where a component does not claim the exception, the specification should still record why, because the components most likely to be misused as a basis for the claim are the ones that never had a basis for it (clause 11.2).

And one prohibition worth quoting at a design review: adopting a widget role in order to unlock the exception is not recorded as a rationale, because doing so abuses both the role and the criterion, and a consumer encountering such a rationale should treat the package as defective (clause 11.2). That is a consumer-side instruction as well as a producer-side one: if you are reviewing somebody else's package and find role="grid" justified by a wish to avoid reflowing content, you have found a defect, not a design choice.

The test resolves as follows. A region qualifies when a cell's significance depends on its relationship to both a row axis and a column axis, so that flattening the structure would destroy meaning rather than merely rearrange appearance (clause 22.2). Stated as a slogan, which is how it is easiest to remember: cells are semantic content, and grid is a layout technique. A CSS Grid container has no table semantics; declaring display: grid, or wrapping items with a content-driven measurement, creates no row header, no column header and no header-to-cell relationship, so visual grid arrangement is not offered as a basis for the exception (clause 22.2).

A decision table

Most disputes are resolved by finding the closest row. This table records how the core test of clause 11.2 resolves in common cases; clause 22.2 is where the specification sets it out, and it adds no requirement of its own.

ContentBasisExcepted
Results table with genuine row and column header relationshipsA cell's significance depends on both axesYes, as a scoped region
Programme guide organised by channel and timeChannel and time are both meaning-bearing axesYes, as a scoped region
Collection of self-contained cardsArrangement is presentationalNo
Dashboard laid out in grid areasArrangement is presentationalNo
Filter panel beside a results listAdjacency is convenience, not meaningNo

Two readings of that table are wrong and worth naming. The programme-guide row establishes that a meaning-bearing two-dimensional structure need not be a conventional data table, and is not to be read as extending the exception to visual grids generally (clause 22.2).

And the exception is scoped. It covers the table as a scoped region, not individual cells, and it does not spread to the page around it. The phrase "not individual cells" in the criterion marks where the semantic two-dimensional relationship stops: the relationship holds between a cell and its two axes, so the excepted thing is the structure that carries the axes, and a single cell has no claim of its own. Scoping is what stops the exception being a licence for a horizontally scrolling page.

Two things follow that belong to Part III rather than here. Making the rationale specific — naming both meaning-bearing axes, explaining how a cell's significance depends on each, stating the semantic structure that carries the relationship, and stating the boundary of the excepted region — is the scoped reflow profile, afds-reflow-scoped, clause 22.3, and so are the profile's rules about surrounding content, truncation and techniques, clauses 22.4 to 22.6. Section D of this guide covers them as a declarable profile. Do not present them as universal rules: a package that claims no profile is bound by clauses 11.1 and 11.2 and by nothing in clause 22 (clause 4.3).

Recording WCAG criteria against a component

Who this serves: testers and developers; this is the field an auditor will read first.

The wcagMapping array contains one entry for every success criterion the component bears on (clause 12.1).

Each entry records six things (clause 12.1).

FieldWhat it records
Criterion numberFor example 2.1.1
Criterion nameFor example Keyboard
Assigned levelThe level at which WCAG 2.2 assigns the criterion
branchOne of the two values in clause 12.2
relationshipOne of the two values in clause 12.3
NoteProse saying what the component does about the criterion

Keep two kinds of "level" apart. The assigned level is a property of the criterion and is fixed by WCAG; it is not the target level of clause 12.4, which is a property of the package or the component and is chosen by the author (clause 12.1).

The two branches

Every entry records a branch of either user technology support or user layout support (clause 12.2).

BranchCovers
user technology supportAssistive-technology compatibility: roles, accessible names, states, focus, keyboard operation
user layout supportReflow, measure, spacing, contrast, reading order

The split is diagnostic (clause 12.2). A flat list of criteria per component hides whether a failure is geometric or semantic, and those two failures have different owners, different tests and different fixes. Classification follows what carries meaning rather than the mechanism that produced the appearance, and the clearest case is the two-dimensional exception, which looks like a layout matter and is decided by semantics (clause 12.2).

The relationship vocabulary

Each entry records a relationship of either supports or does-not-address (clause 12.3).

ValueMeaning
supportsThe component contributes to meeting the criterion
does-not-addressThe component bears on the criterion and does nothing about it, so the consumer owns it

The vocabulary is closed, and extending it is a change to the specification rather than something done inside a package (clause 12.3).

A does-not-address entry is not an admission of failure and is not to be treated as one (clause 12.3). Recording that a layout primitive conveys no relationships, and that the consumer therefore owns Info and Relationships, is more useful than silence, because silence leaves the consumer to discover the ownership in an audit (clause 12.3).

The target level is declared, not mandated

The specification does not fix a target WCAG conformance level and is not to be read as requiring one (clause 12.4). A package declares a default target level; a component may amend it, and a component that amends it records the amended level and the reason (clause 12.4). A method profile may set a default for packages claiming it, and where a package claims such a profile that default governs the package, because a profile is claimed whole (clauses 12.4, 20.3).

The effective level for a component resolves in one order, first available declaration governing: the component's own declaration; the default set by a claimed method profile; the package default (clause 12.4).

Three inferences are forbidden. An effective level is not inferred from anything other than those three declarations; it is not inferred from a completeness profile, which states completeness and says nothing about level; and it is not inferred from the presence of evidence recorded at a higher threshold, because measuring a ratio is not the same act as committing to it (clause 12.4).

And the declaration is not evidence. A declared target level is a statement of intent and is not read as evidence that the level is met (clause 12.4). Whether a criterion is met at the declared level is an assertion under clause 15, substantiated under clause 16.

Amending a level downward is permitted and is recorded rather than concealed (clause 12.4). A component targeting Level AA inside a package that defaults to Level AAA is a disclosure, and the disclosure is worth more than a package-wide claim a reviewer would have to disprove component by component (clause 12.4). A level is declared per component and not per criterion; a package needing to hold one criterion to a different threshold does so as an assertion under clause 15, not as a second target level (clause 12.4).

Kinds of requirement

Who this serves: everyone in a review argument.

Every requirement in a component specification is tagged with exactly one of five kinds (clause 13).

ValueWhat it meansConsequence if not met
required-by-standardA normative requirement from a W3C standardA conformance failure
recommended-by-conventionAn interoperable convention users are likely to expectA usability and discoverability risk, not a conformance failure
project-conventionA choice the system has made for internal consistencyAn inconsistency to be reconciled or documented
product-deviationA deliberate, recorded departure for a product reasonNothing, provided the record and its reasoning exist
support-limitationA gap in browser or assistive-technology behaviourUncertainty to be disclosed, not a claim to be made

Use those spellings. The second value in particular is recommended-by-convention, not "strongly recommended by APG": the core vocabulary does not name one external pattern library, and clause 24.8 records the renaming as deliberate.

Tagging prevents two opposite failures (clause 13). The first is presenting every convention as conformance law. A component may satisfy WCAG with a keyboard model that departs from a widely used convention, provided it is fully operable and its state is correctly conveyed, and a document that denies this loses its authority the moment somebody checks (clause 13). The second is dismissing conventions as merely optional, which is how components end up technically conformant and practically unusable by people who already know how the interaction is supposed to work (clause 13).

The correct handling of a departure is to allow it, label it, and state its cost (clause 13). That last part is a requirement, not a nicety: a product-deviation records its cost as well as its reason (clause 13). The same tag vocabulary is what clause 9.3 means when it requires each deviation from a published pattern to be tagged (clause 9.3).

Guarantees, and what a component refuses to promise

Who this serves: developers writing the contract, and any developer consuming somebody else's.

Guarantees

A guarantee is a declared commitment about the component's behaviour or properties (clause 14.1).

The guarantees array contains one entry per commitment, and each entry records five things (clause 14.1).

FieldWhat it records
idUnique within the specification
statementThe commitment, written so that it can be tested
branchUnder clause 12.2
requirementKindUnder clause 13
assertionsAn array of assertion identifiers

A guarantee is a design commitment: it is what the component is obliged to do, and it outlives any particular test run, which is why it is authored rather than computed (clause 14.1).

A guarantee has to name its test

Every guarantee names at least one assertion that tests it, and a guarantee whose assertions array is empty is invalid — a package containing one does not conform (clause 14.2).

This is the rule that stops a guarantee from being a wish (clause 14.2). A commitment that nobody can state a procedure for is not a commitment about the product, it is a sentiment about it, and the specification says the distinction is the reason the format exists (clause 14.2).

It is also the rule the old guide's worked Dialog broke seven times over: it listed seven guarantees, none of which named an assertion, which made the guide's own example a non-conforming specification. The rewritten example later in this section fixes that.

The rule cannot be discharged by writing an assertion that restates the guarantee without giving a procedure (clause 14.2). Clause 15.1 requires a procedure for exactly this reason. Note what the rule does not require: naming an assertion is not the same as having run it, and a brand-new component with complete guarantees and no evidence is in a perfectly describable state — see substantiation below.

Substantiation is computed, not written

Every guarantee has a substantiation status, and the status is not authored (clause 14.3). It is computed from the evidence records that reference the guarantee's assertions, and a producer that writes it into the specification is stating something it is not entitled to state (clause 14.3).

There are four statuses, not three.

StatusComputed when
substantiatedEvery named assertion has at least one evidence record with result supported, and none with partial or unsupported
partially-substantiatedAt least one named assertion has a result of supported, and at least one has partial or has no record at all
unsubstantiatedNo named assertion has any evidence record other than not-yet-tested
contradictedAny named assertion has an evidence record with result unsupported

partially-substantiated is the one most often missing from summaries of this format, and it is the status most real components are in.

The separation of the promise from the measurement is the point of the design (clause 14.3). A new component with no testing has made commitments and has substantiated none of them, and both halves of that sentence are true and useful. Collapsing them would either let a package promise what it has not earned, or force it to promise nothing until testing exists, and neither describes the real state of any design system (clause 14.3).

Two consequences. A consumer does not present a guarantee as met without also presenting its substantiation status (clause 14.3) — if you are building a documentation site or a component browser over an AFDS package, that is a requirement on your tooling, not on the package. And a contradicted guarantee is a defect in the package rather than a property of the component: the producer either fixes the component, narrows the guarantee, or withdraws it, and Part IV states what each of those does to the version (clause 14.3).

One caution, from the companion document rather than from the specification. Guarantees do not union across an assembly boundary. A child's guarantee propagates into a parent composition only if its preconditions still hold after assembly, the parent has not overridden the relevant semantics or behaviour, no sibling conflicts with it, the author obligations it depends on have been met, and the evidence covers the resulting configuration; otherwise it is suspended and has to be re-established by evidence at the parent level. That analysis is set out in Component Design Frameworks and the Assembly Problem (research/COMPONENT-FRAMEWORKS.md, §5.2). Read a guarantee as conditional, not as a label permanently attached to a component name.

What a component refuses to promise

A non-guarantee is an explicit statement of something the component does not commit to (clause 14.4).

The nonGuarantees array is present and is not empty (clause 14.4). A component that commits to everything has not understood the question (clause 14.4). There is no such thing as a conforming component specification with an empty non-guarantees list, so a review that finds one has found a defect and not a tidy component.

A non-guarantee is specific enough to change what a consumer does (clause 14.4). The specification gives the contrast worked out. "The component does not guarantee accessibility" is not a non-guarantee, because no consumer can act on it. "The component provides no grouping role and no accessible name, so the consumer must supply both" is a non-guarantee, because it tells the consumer what to build (clause 14.4).

Why the list is mandatory at all

This is the mechanism behind clause 2.5, and it is the part of the format that most changes how documentation reads.

A component that lists only its promises invites the reader to assume the rest, and the assumption is where accessibility is lost (clause 14.5). The reader is not being careless when they do this. A list of guarantees reads as a description of the component, and a description is naturally taken to be complete (clause 14.5).

Requiring the opposite list forces the boundary of the contract to be drawn explicitly, by the party that knows where it lies (clause 14.5). Honesty about limits is not a hedge here; it is the load-bearing structure.

Assertions

Who this serves: testers above all.

An assertion is a statement about the component whose truth can be evaluated against an implementation (clause 15.1).

Each entry in the assertions array records four things (clause 15.1).

FieldWhat it records
idUnique within the specification
typeEither automated or manual
statementWhat is asserted
procedureHow to evaluate it

The procedure field is specific enough that two testers following it independently would agree on the result (clause 15.1). A procedure that restates the statement in the imperative does not satisfy that, and is not used to discharge clause 14.2 (clause 15.1). "Check that focus is trapped" is a restatement. "Load the isolated fixture, open the dialog from the trigger button, press Tab eleven times, and record the accessible name of the focused element after each press" is a procedure.

Automated and manual

Both are assertions. There is no separate category of "manual checks" sitting outside the array; manual is an assertion type (clauses 15.1, 15.2).

An assertion of type automated is evaluable without human judgement (clause 15.2).

An assertion of type manual records what the tester observes rather than what they conclude (clause 15.2). The distinction matters because a manual assertion phrased as a conclusion invites the tester to supply the answer the specification expects (clause 15.2). "The dialog is correctly announced" is a conclusion. "On opening, the screen reader speaks the dialog's accessible name followed by its role" is an observation.

A manual assertion produces a result that expires, and clause 16.4 governs that (clause 15.2).

What an assertion is not

An assertion is not a statement about intent, about the design process, or about a standard (clause 15.3).

That a component was built following a pattern is not an assertion, because no procedure evaluates it against the running implementation (clause 15.3). That a component meets a success criterion is not an assertion either, because meeting a criterion is a conclusion drawn from observations rather than an observation. The assertion is the observation (clause 15.3).

This is the rule that keeps the wcagMapping array and the assertions array doing different jobs. The mapping records which criteria the component bears on; the assertions record what somebody can go and look at.

Evidence records

Who this serves: testers and QA engineers.

What evidence attaches to

Evidence attaches to a combination, not to a component (clause 16.1).

A combination is the tuple of assistive technology, browser, engine, operating system, and their versions (clause 16.1). A result observed in one combination says nothing about another, and a package that records a single undifferentiated result is making a claim it has not tested (clause 16.1). The specification calls this the third of its five gaps, and the one that most often survives into otherwise careful documentation (clause 16.1).

The fourteen required fields

The old guide described five fields. Clause 16.2 requires fourteen.

#FieldContent
1idUnique within the package
2componentIdThe component the record concerns
3assertionRefThe assertion or assertions this record evaluates
4claimThe behaviour that was looked for
5engine, engineVersionThe rendering engine and its version
6browser, browserVersionThe browser and its version
7at, atVersionThe assistive technology and its version, or none
8platform, deviceThe operating system and the class of device
9startingViewport, zoomThe layout conditions, or not-applicable
10dateThe date of observation
11resultA value from clause 16.3
12observationWhat was actually observed
13testerWho made the observation
14uncertaintyRefThe uncertainty record this result bears on, where one exists

Two rules make the record usable.

assertionRef is what allows clause 14.3 to compute a substantiation status, and a record that evaluates nothing nameable cannot contribute to a guarantee, so a producer does not record one (clause 16.2). An evidence record with no assertion reference is not weak evidence; it is not evidence.

observation records what happened rather than whether it was correct (clause 16.2). The result field carries the judgement, and keeping the two apart is what makes a record re-readable when the expectation later changes (clause 16.2).

The result vocabulary

result takes one of five values (clause 16.3).

ValueMeaning
not-yet-testedNo observation has been made. The claim it would support is uncertainty, not a guarantee.
supportedThe expected behaviour was observed on the stated versions on the stated date.
partialThe behaviour was observed but differs materially from the expectation. The difference is described.
unsupportedThe expected behaviour was not observed.
not-applicableThe combination cannot exhibit the behaviour.

not-yet-tested is the value to use for a planned combination nobody has run yet. An empty cell, a dash, or the words "to be recorded" are not values in this vocabulary, and a package using them has not recorded a result at all.

The value not-applicable carries a second sense outside the result field: in any other field it means the field does not apply to that record, such as a zoom level on a record about announcement, or an assistive-technology version on a record whose at is none. A package uses it in only those two senses (clause 16.3).

Results expire

An evidence record is an observation on a date, and is not treated as a permanent property of the component (clause 16.4).

A consumer should treat a record as stale when the stated versions are no longer current, and does not present a stale record as a current result without saying so (clause 16.4). That is a requirement on tooling that displays evidence, not only on the package.

This is why the fourth testing level is recorded with a date rather than a tick (clause 16.4). Assistive-technology behaviour changes with releases the package cannot observe, and a format that stores the result without the date stores a claim that quietly becomes false.

Uncertainty records

Who this serves: everyone; this is the field that most distinguishes an AFDS package from ordinary accessibility documentation.

An uncertainty record states that something is not known (clause 17.1).

Each entry in the uncertainty array records five things (clause 17.1).

FieldWhat it records
idUnique within the specification
subjectWhat the uncertainty is about
statementWhat specifically is not known
statusFrom clause 17.2
evidenceRefThe evidence records that bear on it, where any exist

The status vocabulary

status takes one of four values (clause 17.2).

ValueMeaning
not-yet-testedNo observation has been attempted
results-conflictObservations disagree across combinations, and the disagreement is not yet explained
no-known-methodNo procedure is known that would settle the question
awaiting-supportThe question cannot be settled until support changes in a browser or assistive technology

The four are not interchangeable, and choosing between them is a genuine judgement. not-yet-tested says somebody has to go and look. results-conflict says somebody has looked twice and got two answers, which is a different task: explain the divergence. no-known-method is the honest label for a claim nobody knows how to check, and it is the one that most often gets silently downgraded to not-yet-tested. awaiting-support says the answer will change when a vendor ships something, and it is the status that pairs with a reassessment trigger.

Reassessment triggers and support-dependence

Where a component declares that its pattern is support-dependent under clause 9.3, it records a reassessment trigger stating the condition under which its specification is reopened (clauses 9.3, 9.5). In practice the trigger and the uncertainty record are two halves of one disclosure: the uncertainty record, usually with status awaiting-support or not-yet-tested, says what is not known, and the trigger says what would have to happen for the contract to be reopened.

Write the trigger as a condition somebody could notice. Name the combination, the behaviour, and the change that would matter. A trigger that names no condition is not a trigger, and a support-dependent component without one is incomplete under clause 9.5.

Uncertainty is a record, not a failure

An uncertainty record has the same standing as a record stating a result, and a consumer does not treat its presence as a defect (clause 17.3).

A package with no uncertainty records is either exhaustively tested across every combination or is concealing something, and the specification observes that the first is not achievable (clause 17.3).

The rule that does most of the work in the whole format is here. An assistive-technology claim without a test record is recorded as uncertainty rather than as a guarantee (clause 17.3). The reasoning is worth reading twice: the ordinary way accessibility documentation becomes false is not by lying, it is by stating a reasonable expectation in the same voice as a measured result, and this rule makes the two grammatically distinct (clause 17.3).

Testing levels, and testing in composition

Who this serves: testers and QA engineers, and developers deciding what to automate.

The five levels

A package should verify each component at five levels, and each level catches a class of defect the others miss, so they are not treated as substitutes for one another (clause 18.1).

LevelWhat is tested
1. Static semanticsElement choice, role validity, accessible name, state, relationships
2. Keyboard contractEntry, internal movement, activation, exit, restoration
3. Visual and layoutFocus visibility, forced colours, 400 per cent zoom, text spacing, reflow
4. Assistive technologyActual behaviour by combination, version, and date
5. CompositionBehaviour among landmarks, headings, and realistic content

Levels 1 to 3 are largely scriptable and should run on every change. Level 4 is manual, slow, and produces results that expire, which is why clause 16 records it with a date. Level 5 is the level most often skipped, and it is where component-level correctness turns into page-level failure (clause 18.1).

Level 4 is recorded by combination, version and date — combination being the tuple clause 16.1 defines, not a browser name alone.

Component conformance and composition conformance are different claims

Conformance is measured at two levels: the component in isolation, and the component inside a realistic page (clause 18.2).

A package does not claim composition conformance on the strength of isolated testing (clause 18.2). That is the prohibition to remember, and it is the one an audit will test you against. A green test suite over isolated fixtures is evidence about isolated fixtures.

The two levels find different defects, and the composition defects are the ones a component cannot detect about itself (clause 18.2). The specification gives three examples. Two components that each correctly manage focus can produce a page in which focus is managed twice. A component that correctly contributes a landmark can produce a page with duplicate landmarks. A dialog that passes every isolated test can open beneath page chrome its own fixture does not contain (clause 18.2).

Beyond that, the analysis of why assembly breaks — the assembly hierarchy from token to process, the eight compositional failure modes, worked composite breakdowns, state propagation and its ownership rules, and testing across multi-page flows — is carried in Component Design Frameworks and the Assembly Problem (research/COMPONENT-FRAMEWORKS.md). That document is published ahead of this material, and this guide does not restate it. Two of its findings bear directly on how you read Part II. Guarantees do not union across an assembly boundary (§5.2), which is why the guarantee section above treats a guarantee as conditional. And component-level green is not evidence for the page (§7.4), which is the practical form of the clause 18.2 prohibition. Where that document and the specification differ in emphasis, the specification governs and the companion carries the detail.

Fixtures

The tests object records the location of an isolated fixture and of a realistic-page fixture (clause 18.3).

Where a package does not ship a fixture, the tests object records where it belongs and states that it is absent, and a consumer treats the fixture as absent rather than as unlocatable (clause 18.3).

The reasoning generalises the clause 7.2 rule. A recorded path to a fixture that does not exist is a statement about the package's completeness, and a package that quietly omits the field makes the same statement without disclosing it (clause 18.3).

Annotating a design

Who this serves: designers, and the developer receiving a handoff.

A visual mock-up cannot show what a component promises, which keys operate it, where focus goes when a dialog closes, or what happens at high zoom. In a system, those live in the component's specification, and the mock-up is annotated to say which component was chosen and what product-specific decisions apply.

Where a package supports a design-tool handoff, it should provide an annotation preset exposing the information a visual mock-up cannot convey, and the preset should carry eleven fields (clause 19.1). Note the strength: this is a conditional expectation, not a requirement on every package.

FieldWhat it records
Pattern identityWhich pattern, if any, the component implements
Semantic modelNative element and any ARIA roles
Accessible name sourceWhere the name comes from, and whether visible text is contained in it
Relationship modelControlling, expanding, labelling, describing, and error-message relationships
Focus order and initial focusReading and focus sequence, and the initial focus target
Internal keyboard navigationWhich keys move focus inside the component
Close and restore-focus behaviourHow the component is dismissed and where focus returns
Hidden versus removedWhether content is hidden, made inert, or removed from the document
Required visible statesWhich states must be visible; focus is mandatory and hover is optional
Responsive and reflow behaviourHow the component behaves at narrow widths and at high zoom
Assistive-technology uncertainty markerBehaviour known to vary or not yet verified

"If any" in the first row is doing work: a native-first component implements no pattern, and the honest annotation says so.

These eleven are written for design handoff. They are not the fields required by clause 9.3, which are written for engineering review, and a count of one is never a count of the other (clause 19.1). Nor are they the twelve review items of clause 24.4, a third list with a third audience.

The relationship model is the field most often lost and the one that most repays recording, because it is invisible in a mock-up and expensive to reverse-engineer afterwards (clause 19.1). A designer who has decided that a control expands a panel has already decided that an expansion relationship applies, and writing it down costs less than discovering it in an audit (clause 19.1).

The annotation economy rule

An annotation should not restate behaviour the coded component already guarantees (clause 19.2).

The annotation identifies the selected component and any product-level choices or deviations. Restating guaranteed behaviour makes annotations long, makes them drift from the code, and trains reviewers to skim them, which defeats the annotations that carry something the code does not (clause 19.2).

So the rule in working form: do not annotate what the visual design, the component API or the coded component already guarantees. The project takes this from GitHub's published design-system annotation practice — Jan Maarten, Design system annotations, part 1: How accessibility gets left out of components and part 2: Advanced methods of annotating components, The GitHub Blog, at github.blog and github.blog. Clause 19.2 states the rule unattributed; the attribution is recorded here because the practice is somebody else's.

A worked component: Dialog

Who this serves: all three readers. This is the section to copy from.

A modal dialog is the right example because it is genuinely composite, has no complete native equivalent in the shape most products need, and depends on behaviour that varies across assistive technologies. It is also where the previous version of this guide went wrong, in six ways: an invalid derivation.status, seven guarantees naming no assertions, a four-key table standing in for the eight mandatory stages, no semantic model, no reassessment trigger despite declared support-dependence, and evidence cells reading "to be recorded" instead of not-yet-tested. What follows is written to conform.

The specification below is complete against clause 7.2: all seventeen fields are present, and none is omitted on the grounds that it does not apply. Field values are illustrative; the structure is not.


{
  "afdsSpecVersion": "1.0",
  "id": "dialog",
  "name": "Dialog",
  "kind": "component",
  "version": "1.2.0",
  "status": "proposed",
  "summary": "A modal dialog that interrupts the current task to obtain a decision or show content that must be dealt with before continuing. It manages its own focus while open and restores focus when it closes. It does not supply page-level scroll locking, does not make its own destructive actions reversible, and does not guarantee identical announcement across assistive technologies.",
  "semanticModel": {
    "role": "dialog",
    "implicitElement": "div",
    "accessibleName": "aria-labelledby, referencing the required heading element inside the dialog",
    "rationale": "The dialog role with aria-modal is used because the component must convey to assistive technology that content outside it is unavailable while it is open. The native baseline considered was the HTML dialog element with showModal, which supplies the role, the top layer, inertness of the rest of the document, and Escape-to-close. It was judged insufficient for this package because the product requires a dialog that can be rendered inside a constrained stacking context supplied by the host application shell, which the top layer does not permit, and because the package must record dated evidence for announcement behaviour that varies independently of the element used. The role is therefore authored rather than inherited, and the behaviours the native element would have supplied are declared as guarantees with assertions.",
    "domOrderIsReadingOrder": true,
    "consumerObligations": [
      "The consumer MUST supply a heading element as the first child of the dialog's content region, and MUST NOT remove it, because the accessible name is derived from it.",
      "The consumer MUST render the dialog as a child of the application shell's dialog host element, and MUST NOT render it inside a transformed or clipped ancestor.",
      "The consumer MUST provide a visible control that dismisses the dialog, in addition to Escape.",
      "The consumer MUST NOT open a second dialog while this one is open; nesting is excluded by this component's composition rules.",
      "Where the control that opened the dialog may be removed while the dialog is open, the consumer MUST name the logical successor element required by the keyboard contract's restoration stage."
    ]
  },
  "derivation": {
    "status": "pattern-derived",
    "patternName": "Dialog (Modal)",
    "patternSourceUrl": "https://www.w3.org/WAI/ARIA/apg/patterns/",
    "nativeAlternativeConsidered": "HTML dialog element with showModal()",
    "nativeAlternativeInsufficientBecause": "showModal() promotes the element to the top layer, which the host application shell's constrained stacking context does not permit. The product requires the dialog to be clipped by its host region in one embedded deployment. This is an insufficiency finding, not an open question.",
    "deviations": [
      {
        "id": "dev-1",
        "from": "The published pattern's expectation that the dialog is the only modal surface and that content outside it is inert",
        "deviation": "Inertness is applied to a named application-shell subtree rather than to the whole document.",
        "reason": "The embedded deployment renders the host application inside a third-party frame that the package does not control.",
        "cost": "Content outside the named subtree remains reachable by an assistive technology's virtual cursor in that deployment, so a screen-reader user can read content the sighted user cannot reach. This is disclosed as non-guarantee ng-2 and as uncertainty unc-2.",
        "requirementKind": "product-deviation"
      }
    ],
    "supportDependent": true,
    "reassessmentTrigger": "Reopen this specification when either of the following occurs: the recorded result for assertion a-4 (dialog role and name conveyed on opening) changes from `partial` to `supported` for JAWS with Chrome across two consecutive JAWS releases; or any tested combination's aria-modal handling changes such that content outside the inert subtree stops being reachable by the virtual cursor. The owner named in the package manifest re-runs the level 4 fixtures on each major release of any assistive technology in the recorded matrix."
  },
  "keyboardContract": {
    "hasKeyboardContract": true,
    "stages": {
      "entry": "On opening, focus moves to the first interactive control in the dialog's content region, or to the dialog's own container where the content region contains no interactive control. On re-entry after the dialog has been closed and reopened, the same rule applies; the dialog holds no memory of previous focus position.",
      "internalMovement": "Tab and Shift+Tab cycle through the dialog's interactive controls in document order, wrapping at both ends. Arrow keys are not bound by the dialog and are passed to the focused control. Neither roving tabindex nor aria-activedescendant is used; focus is real DOM focus on each control.",
      "activation": "Enter commits the action of the focused control and nothing else. Space activates the focused button and toggles the focused checkbox. No key changes selection at the dialog level, so selection and commitment do not overlap: the dialog itself has no selectable items, and any selection behaviour belongs to a control the consumer places inside it.",
      "exit": "Tab does not leave the dialog; movement wraps within it while the dialog is open. Escape dismisses the dialog, and dismissal is equivalent to activating the visible cancel control. On Escape, focus returns to the restoration target defined below. The visible cancel control is required by consumer obligation 3 so that the exit path does not depend on the user guessing Escape.",
      "stateChange": "Opening is conveyed by focus moving into the dialog and by the dialog's role and accessible name being exposed at the newly focused element's context; no live region is used for opening. Validation failure inside the dialog is conveyed by the failing control's aria-describedby reference to its error message and by aria-invalid on that control. A loading state in the dialog's content region is conveyed by aria-busy on the content region. Deletion of the dialog's content is not a state this component conveys, and is disclosed as non-guarantee ng-3.",
      "restoration": "On close, focus returns to the element that opened the dialog. Where that element no longer exists, focus returns to the documented logical successor: the nearest preceding interactive sibling of the removed invoker within the same list or toolbar, or, where none exists, the container that held the invoker, which the consumer makes focusable for this purpose. The successor is named in the consumer obligation set and is not left to the user agent.",
      "pointerAndTouchParity": "All functionality is reachable without hover, without drag, and without path-dependent pointer movement. The dialog exposes no hover-only affordance. Dismissal is available by pointer on the visible cancel control, by Escape, and by pointer on the backdrop where the consumer enables that option.",
      "speechRecognitionOperation": "Every visible interactive control in the dialog carries a stable visible label, and each control's accessible name contains its visible text. The cancel control's visible label is Cancel and its accessible name is Cancel. The dialog's own accessible name is the visible heading text."
    },
    "focusLifecycle": {
      "receivesFocus": true,
      "movesFocus": true,
      "trapsFocus": true,
      "restoresFocus": true,
      "note": "This component traps focus for the whole time it is open, and restores focus on close. Two focus-trapping components on one page produce a defect neither component's own tests can detect, so this component's composition rules exclude a nested dialog and exclude any other trapping component inside its content region. A consumer assembling a page checks this combination before composing."
    }
  },
  "reflowBehaviour": {
    "isIntrinsic": true,
    "usesLayoutMediaQueries": false,
    "authorFixedDimensions": "none",
    "declaresFixedHeights": false,
    "mechanism": "The dialog's content region is a single-column flow whose width is bounded by a maximum measure expressed in rem and by the available width of its host, whichever is smaller. At narrow widths the dialog occupies the full host width less its gutter, and its content region scrolls vertically. Nothing in the dialog is positioned by breakpoint.",
    "operatesWithoutJavaScript": false,
    "claimsTwoDimensionalException": false,
    "twoDimensionalExceptionRationale": "The exception is not claimed. The dialog's content has no meaning-bearing second axis: its children are a heading, a flow content region, and an action group, none of whose significance depends on a relationship to both a row axis and a column axis. Where a consumer places a data table inside the dialog, the exception belongs to that table as a scoped region and not to the dialog, and the claim is made in the table's own specification."
  },
  "wcagMapping": [
    {
      "criterion": "1.3.1",
      "name": "Info and Relationships",
      "assignedLevel": "A",
      "branch": "user technology support",
      "relationship": "supports",
      "note": "The dialog exposes the dialog role and derives its accessible name from the required heading, so the interruption is conveyed programmatically."
    },
    {
      "criterion": "1.4.10",
      "name": "Reflow",
      "assignedLevel": "AA",
      "branch": "user layout support",
      "relationship": "supports",
      "note": "The dialog is intrinsic and declares no author-fixed dimensions or fixed heights; it does not claim the two-dimensional exception."
    },
    {
      "criterion": "2.1.1",
      "name": "Keyboard",
      "assignedLevel": "A",
      "branch": "user technology support",
      "relationship": "supports",
      "note": "All eight keyboard-contract stages are declared, and no functionality requires a pointer."
    },
    {
      "criterion": "2.1.2",
      "name": "No Keyboard Trap",
      "assignedLevel": "A",
      "branch": "user technology support",
      "relationship": "supports",
      "note": "Focus is confined while the dialog is open, and Escape plus the required visible cancel control provide a documented exit."
    },
    {
      "criterion": "2.4.7",
      "name": "Focus Visible",
      "assignedLevel": "AA",
      "branch": "user layout support",
      "relationship": "does-not-address",
      "note": "The dialog supplies no focus indicator for the controls a consumer places inside it. The consumer owns focus visibility for those controls."
    },
    {
      "criterion": "4.1.2",
      "name": "Name, Role, Value",
      "assignedLevel": "A",
      "branch": "user technology support",
      "relationship": "supports",
      "note": "Role, name and modal state are exposed; the announcement of the combination is uncertain across combinations and is recorded as unc-1."
    }
  ],
  "guarantees": [
    {
      "id": "g-1",
      "statement": "While open, the dialog exposes role dialog and an accessible name derived from the heading element in its content region.",
      "branch": "user technology support",
      "requirementKind": "required-by-standard",
      "assertions": [
        "a-1",
        "a-4"
      ]
    },
    {
      "id": "g-2",
      "statement": "While open, Tab and Shift+Tab move focus only among the dialog's own interactive controls, wrapping at both ends.",
      "branch": "user technology support",
      "requirementKind": "required-by-standard",
      "assertions": [
        "a-2"
      ]
    },
    {
      "id": "g-3",
      "statement": "Escape closes the dialog, and closing by Escape has the same effect as activating the visible cancel control.",
      "branch": "user technology support",
      "requirementKind": "recommended-by-convention",
      "assertions": [
        "a-3"
      ]
    },
    {
      "id": "g-4",
      "statement": "On close, focus returns to the invoking element, or to the documented logical successor where the invoker no longer exists.",
      "branch": "user technology support",
      "requirementKind": "required-by-standard",
      "assertions": [
        "a-5",
        "a-6"
      ]
    },
    {
      "id": "g-5",
      "statement": "At a 1280 CSS pixel starting viewport at 400 per cent zoom, the dialog presents its content without two-dimensional scrolling and without loss of information or functionality.",
      "branch": "user layout support",
      "requirementKind": "required-by-standard",
      "assertions": [
        "a-7"
      ]
    },
    {
      "id": "g-6",
      "statement": "Every visible interactive control the dialog supplies has an accessible name containing its visible label text.",
      "branch": "user technology support",
      "requirementKind": "required-by-standard",
      "assertions": [
        "a-8"
      ]
    }
  ],
  "nonGuarantees": [
    {
      "id": "ng-1",
      "statement": "The dialog does not prevent the document behind it from scrolling. The consumer must apply scroll locking to the application shell, or accept that background scrolling remains available."
    },
    {
      "id": "ng-2",
      "statement": "In the embedded deployment described in deviation dev-1, the dialog does not make content outside the named application-shell subtree unreachable. A screen-reader user can read content outside the dialog with a virtual cursor. The consumer must not use the embedded deployment where that is unacceptable."
    },
    {
      "id": "ng-3",
      "statement": "The dialog does not convey the deletion or replacement of its own content region while open, and supplies no live region for it. A consumer whose dialog content changes in place must supply the announcement."
    },
    {
      "id": "ng-4",
      "statement": "The dialog does not make destructive actions reversible and supplies no confirmation step of its own. A consumer placing a destructive action in the dialog must supply the confirmation or the undo."
    },
    {
      "id": "ng-5",
      "statement": "The dialog does not guarantee that every browser and assistive-technology combination conveys its role and name in the same words or in the same order. See uncertainty unc-1."
    }
  ],
  "assertions": [
    {
      "id": "a-1",
      "type": "automated",
      "statement": "The dialog's outermost element has role dialog, has aria-modal set to true, and has an aria-labelledby reference resolving to a non-empty accessible name.",
      "procedure": "Load fixtures/dialog/isolated.html. Activate the element with id trigger. Query the element with id dialog. Read its role, aria-modal attribute, and computed accessible name from the accessibility tree exposed by the test driver. Record role, the aria-modal value, and the accessible name string."
    },
    {
      "id": "a-2",
      "type": "automated",
      "statement": "With the dialog open, pressing Tab from the last interactive control moves focus to the first interactive control, and Shift+Tab from the first moves focus to the last.",
      "procedure": "Load fixtures/dialog/isolated.html and open the dialog. Enumerate the interactive controls inside the element with id dialog in document order and record the count n. Press Tab n times from the initially focused control, recording the id of the focused element after each press. Then focus the first control and press Shift+Tab once, recording the id of the focused element. Record the two sequences."
    },
    {
      "id": "a-3",
      "type": "automated",
      "statement": "With the dialog open, pressing Escape removes the dialog from the accessibility tree and fires the same close event as activating the control with id cancel.",
      "procedure": "Load fixtures/dialog/isolated.html and open the dialog. Attach a listener recording close events and their detail. Press Escape. Record the event and whether the element with id dialog is present in the accessibility tree. Reload, open the dialog, activate the control with id cancel, and record the same two observations. Record both event details verbatim."
    },
    {
      "id": "a-4",
      "type": "manual",
      "statement": "On opening the dialog, the assistive technology speaks the dialog's accessible name and conveys that it is a dialog.",
      "procedure": "Load fixtures/dialog/isolated.html in the combination under test with the assistive technology running and speech viewer or braille output captured. With focus on the element with id trigger, activate it with Enter. Transcribe, verbatim, everything spoken or output between activation and the end of speech. Do not interpret; record the words in the order they were produced. Two testers compare transcripts for the presence of the heading text and of a word conveying the dialog role."
    },
    {
      "id": "a-5",
      "type": "automated",
      "statement": "On close, focus is on the element that opened the dialog, where that element is still in the document.",
      "procedure": "Load fixtures/dialog/isolated.html. Record the id of the element with focus, activate it to open the dialog, then press Escape. Record the id of the element with focus after close. Repeat, closing by activating the control with id cancel."
    },
    {
      "id": "a-6",
      "type": "automated",
      "statement": "On close, where the invoking element has been removed from the document while the dialog was open, focus is on the documented logical successor.",
      "procedure": "Load fixtures/dialog/invoker-removed.html, which contains a list of rows each with a delete button. Activate the delete button in the third row to open the dialog. From within the dialog, activate the control with id confirm, which removes the third row and closes the dialog. Record the id of the element with focus after close, and record whether it is the delete button of the second row, which is the documented successor."
    },
    {
      "id": "a-7",
      "type": "manual",
      "statement": "At a 1280 by 1024 CSS pixel starting viewport at 400 per cent zoom, all of the dialog's content and controls can be reached by vertical scrolling alone.",
      "procedure": "Set the browser window to a 1280 by 1024 CSS pixel viewport. Load fixtures/dialog/realistic-page.html and set zoom to 400 per cent. Open the dialog. Attempt to reach each control listed in the fixture's manifest using vertical scrolling only, without horizontal scrolling. Record, for each control, whether it was reached and whether any horizontal scrollbar appeared on the dialog or on the document. Record any text that was clipped or truncated without a means of revealing it."
    },
    {
      "id": "a-8",
      "type": "automated",
      "statement": "Each interactive control the dialog supplies has a computed accessible name that contains its visible label text as a substring.",
      "procedure": "Load fixtures/dialog/isolated.html and open the dialog. For each interactive control inside the element with id dialog, read its visible text content and its computed accessible name from the accessibility tree. Record both strings for each control, and record whether the visible text is a substring of the accessible name after collapsing whitespace."
    }
  ],
  "uncertainty": [
    {
      "id": "unc-1",
      "subject": "Announcement of the dialog role and accessible name on opening, across the recorded combinations",
      "statement": "It is not known whether all four combinations in the package's recorded matrix convey both the dialog role and the accessible name on opening. Observation exists for one combination and is outstanding for three.",
      "status": "not-yet-tested",
      "evidenceRef": [
        "ev-a4-nvda-firefox-win"
      ]
    },
    {
      "id": "unc-2",
      "subject": "Reachability of content outside the inert subtree in the embedded deployment",
      "statement": "It is not known whether any tested combination prevents virtual-cursor access to content outside the named application-shell subtree when aria-modal is set but the document root is not inert. The behaviour is a property of the assistive technology and cannot be settled by this package alone.",
      "status": "awaiting-support",
      "evidenceRef": []
    },
    {
      "id": "unc-3",
      "subject": "Announcement of validation failure inside the dialog on mobile screen readers",
      "statement": "Observations disagree: on one combination the error message referenced by aria-describedby was output on focus, and on another it was not, and the difference is not yet explained.",
      "status": "results-conflict",
      "evidenceRef": [
        "ev-a4-talkback-chrome-android",
        "ev-a4-voiceover-safari-ios"
      ]
    }
  ],
  "tests": {
    "isolatedFixture": "fixtures/dialog/isolated.html",
    "realisticPageFixture": "fixtures/dialog/realistic-page.html",
    "additionalFixtures": [
      {
        "path": "fixtures/dialog/invoker-removed.html",
        "present": true,
        "purpose": "Exercises assertion a-6, the removed-invoker restoration case."
      },
      {
        "path": "fixtures/dialog/embedded-shell.html",
        "present": false,
        "absenceStatement": "This fixture is absent. It belongs at the recorded path and would exercise the embedded deployment described in deviation dev-1. A consumer treats it as absent rather than as unlocatable."
      }
    ]
  }
}

One evidence record in full

Evidence lives in the package's evidence records rather than in the component specification, and each record carries all fourteen clause 16.2 fields. Here is one, complete.


{
  "id": "ev-a4-nvda-firefox-win",
  "componentId": "dialog",
  "assertionRef": [
    "a-4"
  ],
  "claim": "On opening the dialog, the assistive technology speaks the dialog's accessible name and conveys that it is a dialog.",
  "engine": "Gecko",
  "engineVersion": "142.0",
  "browser": "Firefox",
  "browserVersion": "142.0",
  "at": "NVDA",
  "atVersion": "2026.2",
  "platform": "Windows 11 26H1",
  "device": "desktop",
  "startingViewport": "1280x1024",
  "zoom": "100%",
  "date": "2026-08-27",
  "result": "partial",
  "observation": "On activating the trigger, speech output was: \"Delete this report, dialog. Cancel, button.\" The accessible name and the word dialog were both output. The aria-modal state was not conveyed in words, and the heading was spoken before the role rather than after it.",
  "tester": "A. Rahman",
  "uncertaintyRef": "unc-1"
}

The remaining three combinations in the recorded matrix have records of their own with result set to not-yet-tested and observation recording that no observation has been made. They are not blank cells and they do not say "to be recorded"; not-yet-tested is the value the vocabulary supplies for exactly this state (clause 16.3).

What that example does to the guarantees

No substantiation status appears anywhere in the specification above, because the status is computed and a producer that writes it in is stating something it is not entitled to state (clause 14.3).

Computing it from the evidence above: guarantee g-1 names assertions a-1 and a-4, and a-4 has a record with result partial, so g-1 computes to partially-substantiated rather than substantiated (clause 14.3). That is the honest state of a real component two weeks into testing, and the format has a word for it.

Checks run against this example

The example was checked field by field against the clauses that govern it.

ClauseCheckResult
7.2All seventeen required fields present, none omitted as inapplicablePresent; reflowBehaviour.claimsTwoDimensionalException is false with a recorded reason rather than absent
7.3, 7.4Identity fields present; kind and status from the fixed vocabulariescomponent, proposed
8.1, 8.2, 8.3Six semantic-model keys; obligations written as consumer requirements in clause 4.1 language; native baseline recorded with an insufficiency findingPresent
9.2, 9.3, 9.5derivation.status is a valid value; pattern name and source URL, native alternative and insufficiency, deviations with reason, cost and clause 13 tag, support-dependence and a reassessment triggerpattern-derived; one deviation with cost, tagged product-deviation; trigger names a condition
10.2, 10.3, 10.4All eight stages declared; stage 3 distinguishes selection from commitment; stage 4 gives focus destinations and does not depend on guessing; stage 6 names a logical successor; four focus-lifecycle booleans with a notePresent
11.1, 11.2Seven reflow items; a recorded reason for not claiming the exceptionPresent
12.1, 12.2, 12.3One entry per criterion, each with number, name, assigned level, branch, relationship and noteSix entries, one does-not-address
13Every requirement tagged with exactly one kind; product-deviation carries its costPresent
14.1, 14.2, 14.4Five fields per guarantee; every guarantee names at least one assertion; non-guarantees present, non-empty and specificSix guarantees, all with assertions; five non-guarantees
15.1, 15.2, 15.3Four fields per assertion; procedures reproducible by two testers; manual assertions record observation rather than conclusion; no assertion about intent, process or a standardEight assertions, five automated and three manual
16.2, 16.3Fourteen evidence fields; result from the closed vocabulary; observation records what happenedOne full record shown, result partial
17.1, 17.2Five uncertainty fields; status from the four valuesThree records, using not-yet-tested, awaiting-support and results-conflict
18.3Isolated and realistic-page fixture locations recorded; an absent fixture recorded as absent at its pathPresent