The attribute exists. Does its target?
An assertion that finds aria-controls can pass while the relationship is broken. In the Panel Association lab, Broken Case 2 gives its tabs controls references, but the mounted content has no matching ID. The string exists. Its destination does not.
The useful regression contract is not "we added ARIA." It is "this tab identifies the intended panel, that panel gets its name from this tab, and keyboard focus reaches the appropriate beginning of the content."
The companion blog explains those jobs. Here we turn them into a repeatable manual test, including checks a screenshot or attribute-presence assertion misses. This is a test plan, not a report of a completed run. Expectations below come from the documented source; no new keyboard, accessibility-tree or screen reader tests were performed for this draft.
Start with one active pair
Open Panel Association and dismiss overlays that obstruct the content. Run Broken Case 1, Broken Case 2, Fixed Case 1 and Fixed Case 2 separately. Scope inspection to the case under test: Account appears repeatedly across the page.
Use native keyboard activation for these tabs. Arrow navigation is outside this scenario's scope; its fixed examples repair semantics, not the complete tabs keyboard pattern.
- Select Account. Inspect the selected tab's ID and
aria-controls. Resolve the controls value to an element in the document. Check that it is the intended visible content container, not another case's panel. - Follow the return reference. Inspect the target's
role="tabpanel"andaria-labelledby. Resolve the label reference to the actual Account tab. Check the computed accessible name in Chrome's accessibility tree, rather than merely finding a label attribute. - Count IDs globally. Each relevant ID must identify exactly one element across the document. Scoping the widget query does not make duplicate IDs elsewhere harmless.
- Activate Notifications. Repeat the target, role, name-source and uniqueness checks. Inspect the new mounted panel, not a saved reference to Account's removed content.
- Inspect inactive references separately. Resolve every tab's controls target, including inactive tabs. Record this stronger check independently from the active-pair result.
Expected from source, not newly measured: Broken Case 1 lacks controls and panel semantics. Broken Case 2 has dangling controls references. Fixed active pairs have the intended role, name and relationship wiring. Their inactive targets can be absent.
A panel named Account is not sufficient proof: the name might come from an unrelated label. Conversely, a controls reference that resolves does not establish the panel's accessible name. Inspect both directions.
The conditional-mount IDREF trap
An IDREF is a reference to an element's ID. Its validity depends on the rendered document, not just matching strings in a React component.
Panel Association conditionally mounts only the active panel. Selecting Notifications removes Account's panel. The inactive Account tab remains, with a controls reference whose target can now be absent, even in the fixed cases.
That creates two distinct contracts:
- Active-pair smoke test: the selected tab points to its mounted panel, which has the correct role and name source.
- All-tabs IDREF invariant: every tab's controls reference resolves, including references from inactive tabs.
The association lab's fixed examples must not be described as passing the second contract. A green active-pair result cannot stand in for it. Keep the stronger assertion intact and document the conditional-mount caveat, rather than quietly filtering the check down to selected tabs.
The Full Tabs Pattern lab provides a contrast: source retains all panels and hides inactive ones. Target existence and visibility are separate questions there. A retained hidden panel can still supply an ID target without becoming a keyboard stop.
For a reusable component, add a controlled two-instance fixture and repeat the global uniqueness check. Existing case namespaces do not demonstrate that multiple dynamic instances are collision-free. This is a proposed regression check, not an existing lab result or a request to change the labs.
Test the first meaningful content
The APG Tabs pattern Keyboard Interaction Note 4 recommends tabindex="0" when the panel has no focusable elements, or its first element with content is not focusable. "There is a button somewhere" answers neither condition reliably.
Use this decision matrix before writing a focus assertion:
| Content layout | Expected entry under APG guidance | Evidence boundary |
|---|---|---|
| Text only | Panel with tabindex="0" | Present in Full Tabs Pattern Fixed |
| First meaningful content is a link or control | First focusable content; an extra panel stop is not automatically needed | Illustrative only, not present in the live labs |
| Text first, button later | Panel stop before the later control | Illustrative only, not present in the live labs |
For the existing text-only example, make a second manual pass in Full Tabs Pattern Fixed:
- Enter the selected tab using the keyboard. Press Right Arrow to Notifications, then Tab. Inspect
document.activeElement: it should be the visible Notifications panel, not a hidden panel or unrelated control. - Inspect its live
tabindex="0"and visually check the keyboard focus indicator. An attribute cannot prove that a ring is visible. - Press Tab again to advance, then reverse with Shift+Tab. Verify the panel-to-selected-tab path without trapping focus. Hidden inactive panels must not become extra stops.
- Compare the accessibility-tree role, name and association before and after panel focus. The focused flag should move; the name source should not change merely because focus moved.
These are planned checks, not fresh pass claims. Link-first and text-first/later-button fixtures would need separate construction and testing after approval. Do not borrow a pass from the text-only case.
Common mistakes and the checks that catch them
- Treating controls as a naming API. Check the computed panel name separately.
aria-controlsexpresses a relationship, not a name. - Labelling the panel from itself or the wrong tab. Follow
aria-labelledbyto its real target and inspect that target's identity, not just its text. - Copy-pasting IDs. Render two widget instances in a controlled fixture and count IDs across the whole document. A single-instance screenshot misses cross-instance collisions.
- Checking only the selected target. Keep the separate all-tabs check. Conditional mounting can leave inactive references dangling.
- Removing the panel stop because a later button exists. Inspect the first meaningful content, not the list of interactive descendants.
- Adding a stop to every panel indiscriminately. For a link-first layout, assess whether another stop is useful instead of applying a blanket rule.
- Calling a name fix a keyboard fix. Maintain separate assertions for role, naming, relationships and focus entry.
- Assuming focusability supplies focus styling. Inspect the actual keyboard indicator in a browser. DOM attributes alone cannot establish visibility.
- Calling skipped static text a keyboard failure. Test whether functionality is keyboard-operable before citing WCAG 2.1.1. Reading navigation is a different check.
Keep the evidence layers separate
If automating later, split active-pair checks, all-tabs target existence and real keyboard navigation into separate tests. Scope role queries to one case and match names exactly. Do not use a global Account locator on this multi-case page. Assert the computed accessible name and actual label target, not textContent alone.
A DOM test cannot establish a visible focus indicator or Chrome's accessibility-tree exposure. Screen reader reading navigation is another layer again: record platform, navigation method and state, and report only observed output. No fresh Mac capture or invented VoiceOver quotation is needed here.
For WCAG, assess 1.3.1 Info and Relationships and 4.1.2 Name, Role, Value in context. The association broken/fixed comparison changes role and naming together; it does not isolate naming alone. APG Note 4 is guidance, not a WCAG requirement. Skipped static text does not automatically establish 2.1.1 Keyboard, and checking a ring does not mean this lab demonstrates a 2.4.7 Focus Visible failure. These checks support diagnosis, not a claim of complete conformance.
Keep practicing
Compare the keyboard landing in Full Tabs Pattern.
Practice this in the Panel Association lab on wasexamprep.com, and review Custom Widgets for the related WAS rules. For the selected state on the same tabs, read The Selected Tab That Lies.
Watch the full walkthrough and subscribe to the A11Y in Practice YouTube channel for more hands-on accessibility episodes.
A11Y in Practice and wasexamprep.com are independent educational resources. They are not affiliated with or endorsed by IAAP (International Association of Accessibility Professionals).


