docs(radio): a11y migration docs - #6609
Conversation
|
📚 Branch Preview Links🔍 Gen1 Visual Regression Test ResultsWhen a visual regression test fails (or has previously failed while working on this branch), its results can be found in the following URLs:
Deployed to Azure Blob Storage: If the changes are expected, update the |
Coverage Report for CI Build 31730242068Warning No base build found for commit Coverage: 96.239%Details
Uncovered ChangesNo uncovered changes found. Coverage RegressionsRequires a base build to compare against. How to fix this → Coverage Stats💛 - Coveralls |
|
|
||
| - **Exception (button-like and radio-like controls):** these put the role on the **host** instead, via `ElementInternals` (`internals.role = 'button'` / `'radio'`). This is safe specifically because neither role needs to expose a *live value* to assistive technology the way a textbox or combobox trigger does; a radio's or button's full state is carried by `aria-checked` or the role itself, so there is no value-mirroring problem to solve on the host. It also collapses the focusable element, the role element, and (for radio) the roving-tabindex participant the group's focus controller drives into a single node, instead of splitting them across host and shadow root. Do not extend this exception to controls that carry a live value (textbox, combobox); see [Why not put the value-bearing role on the host?](#why-not-put-the-value-bearing-role-on-the-host) for why that case does not work the same way. Even with the role on the host, name these controls through `accessible-label` / `accessible-labelledby`, not raw `aria-label` (see [§3.3](#33-idref-strategy-label-help-text-and-errors)). | ||
| - **Consequence:** because a value-bearing field's host has no role, an axe-core scan of the host alone reports a false positive; this is expected and handled by the axe policy in [§3.4](#34-axe-core-policy). | ||
| - **Exception (button-like controls, and container-only roles with no native HTML equivalent, such as `radiogroup`):** these put the role on the **host** instead, via `ElementInternals` (`internals.role = 'button'` / `'radiogroup'`). This is safe specifically because neither role needs to expose a *live value* to assistive technology the way a textbox or combobox trigger does, and neither has a native HTML element it would otherwise be giving up by moving to the host; there is no `<button>` or `<radiogroup>` element being bypassed. Do not extend this exception to controls that carry a live value (textbox, combobox); see [Why not put the value-bearing role on the host?](#why-not-put-the-value-bearing-role-on-the-host) for why that case does not work the same way. Even with the role on the host, name these controls through `accessible-label` / `accessible-labelledby`, not raw `aria-label` (see [§3.3](#33-idref-strategy-label-help-text-and-errors)). |
There was a problem hiding this comment.
Fixing a misconception. The radio used in the radio controller demo is not the example to follow for how to build a radio and where to put the role. It was merely to demo how a single controller could bundle several other related controllers that a radio group would need. The checkbox in this hybrid demo is what we should follow: https://nikkimk.github.io/web-component-form-strategy-demos/demo-hybrid.html
| ### What it is | ||
|
|
||
| - The container for two or more mutually exclusive `swc-radio` items. It owns sibling discovery, enforces that exactly one item is checked, moves roving keyboard focus between items, and is the level at which group-wide concerns (label, description, error message, required, invalid, read-only, disabled) live, rather than any one item. | ||
| - Sets `role="radiogroup"` on its own host via `ElementInternals` (`internals.role = 'radiogroup'`), for the same reason `swc-radio` sets `role="radio"` on its own host: the group's state is fully described by which child has `aria-checked="true"`, so there is no separate live value that needs a different node to carry it. |
There was a problem hiding this comment.
This section of the comment ( "for the same reason swc-radio sets role="radio" on its own host:" ) seems to contradict what it says in the radio analysis.
Radio says:
Renders a real, native <input type="radio"> inside its own shadow DOM to carry role="radio", checked, and keyboard activation natively, the same shadow-DOM-first pattern swc-text-field uses for its <input type="text">. The host itself sets no role. This follows the forms strategy RFC's general rule that the role element defaults to the shadow DOM (see the RFC's §3.2); unlike swc-radio-group (which has no native container element to render and so uses the RFC's host-role exception instead), swc-radio has a native equivalent and does not need that exception.
| | --- | --- | | ||
| | [Radio Group pattern (APG)](https://www.w3.org/WAI/ARIA/apg/patterns/radio/) | `radiogroup` containing `radio` children; roving `tabindex`; arrow keys move focus and selection together; no manual-activation variant. | | ||
| | [Labels or instructions (WCAG 3.3.2)](https://www.w3.org/WAI/WCAG22/Understanding/labels-or-instructions.html) | The group as a whole needs a visible, programmatically associated label, not just labels on the individual radios. [SWC-1178](https://jira.corp.adobe.com/browse/SWC-1178) tracks a report of a missing visible group label in 1st-gen's anatomy documentation; verify against the live `sp-radio-group` API (which does support a `label` property/attribute) rather than assuming the underlying capability is missing, but treat the report as a signal to double-check every 2nd-gen anatomy and Storybook example actually supplies one. | | ||
| | [Info and relationships (WCAG 1.3.1)](https://www.w3.org/WAI/WCAG22/Understanding/info-and-relationships.html) | Grouping, label, description, and error message must all be programmatic (role containment plus `aria-describedby`/`aria-errormessage`), not conveyed by layout alone. | |
There was a problem hiding this comment.
I'm wondering if we will have the same issue that came up on the Divider component, when it came to dev-warnings. Our solution was to create a ticket to update Divider to use an internal element which could trigger a warning on a property change. But it sounds like for these forms we can't go with that pattern.
5t3ph
left a comment
There was a problem hiding this comment.
Looks good besides what @miwha-adobe noted in the radio group plan re: the radio role living on host or using an internal native radio.
| | **Host role** | None. The host sets no `role`. A real, native `<input type="radio">` inside `swc-radio`'s own shadow DOM supplies the implicit `radio` role. This follows the forms strategy RFC's [§3.2](../../05_strategies/forms-strategy-rfc.md#32-where-aria-roles-live) general rule (role element defaults to the shadow DOM), not the host-role exception reserved for controls with no native equivalent, such as `swc-radio-group`'s `radiogroup` container (see the [radio group doc](../radio-group/accessibility-migration-analysis.md#aria-roles-states-and-properties)). Attach the shadow root with `delegatesFocus: true` so Tab and programmatic focus land on the inner `<input>` directly, the same pattern `swc-text-field` uses. | | ||
| | **`checked` / `aria-checked`** | Comes from the browser for free: the native `<input type="radio">`'s `checked` IDL property drives its own implicit `aria-checked`, so `swc-radio` does not need to write `aria-checked` manually the way 1st-gen `sp-radio` does on itself in `updated()`. Keep `swc-radio`'s own `checked` (or `selected`) property in sync with the inner input's `checked`. Radio's `aria-checked` only ever takes `true`/`false`, never `"mixed"` (unlike checkbox's tri-state support). | | ||
| | **Accessible name** | Rendered as a hidden-until-populated label element inside `swc-radio`'s own shadow root (matching the shape of the PoC's [`checkbox-hybrid.js`](https://github.com/nikkimk/web-component-form-strategy-demos/blob/main/checkbox-hybrid.js)), wired by the **`LabellingController`** ([SWC-2466](https://jira.corp.adobe.com/browse/SWC-2466), *pending research*), which points the inner `<input type="radio">`'s (the role-bearing element's) `ariaLabelledByElements` at that span. `ariaLabelledByElements` is a general ARIAMixin element-reference property available directly on any element, including a plain native `<input>`; it does not require `ElementInternals`. In precedence order: (1) `accessible-labelledby`, for the rare case a radio's name is composed from elements it doesn't own, resolved by the controller into the same `ariaLabelledByElements` array; (2) `accessible-label`, for a radio with no visible text label of its own; (3) the slotted label content, the normal case. See [Shadow DOM and cross-root ARIA Issues](#shadow-dom-and-cross-root-aria-issues) for why this is one mechanism for both the slotted and light-DOM-sibling cases, not two. | | ||
| | **Per-item description (open scope question)** | [React Spectrum's `Radio`](https://react-spectrum.adobe.com/RadioGroup) accepts an optional per-radio `description` (used for enriched option text, for example "Delivers in 5–7 business days" under a shipping-method label). 1st-gen `sp-radio` has no equivalent, and the reviewed Figma files show only a group-level description and error message, matching 1st-gen's `sp-radio-group` help-text slots rather than a per-radio one. Whether `swc-radio` adds a per-radio `description` in this migration wave, or defers it, is an open API-scope question for the migration plan (SWC-2350) and Phase 3 (API), not decided here. If it is added, it can use a plain same-root `aria-describedby` pointing at an element in the input's own shadow root, the same pattern `swc-text-field` uses for its description; no element-reference API is needed for this case. | |
There was a problem hiding this comment.
Note: Design very recently confirmed they will be supporting per-item descriptions, so we should plan on it as well (holler if you want the Slack link to that).
Description
In
spectrum-web-components/CONTRIBUTOR-DOCS/03_project-planning/03_components/radio/accessibility-migration-analysis.mdand.../radio-group/accessibility-migration-analysis.md:swc-radioandswc-radio-group, including the host-role exception (role="radio"/role="radiogroup"viaElementInternals) called out in the forms strategy RFCswc-radio-group: roving tabindex, four-direction arrow movement with wraparound, Home/End, disabled-item skipping, and the "arrow movement always selects, Tab-entry never does" split, verified directly against 1st-gen'sRovingTabindexControllertest suite and mapped ontoFocusgroupNavigationControlleraria-invalidever set on the group host; per-radioreadonlythat is unenforced and misplaced relative to React Spectrum's group-levelisReadOnly; deprecated per-radioaria-invalidtracked by SWC-285; unconditionalaria-live="assertive"on help text), plus open questions explicitly deferred to the SWC-2470 research spike (whether a dedicatedRadioGroupControlleris needed) rather than resolved heresp-radioandsp-radio-group, covering current ARIA implementation, keyboard handling (confirmed against source and the existing test suite), and known issues with dispositionsAlso updated the AI friendly version of forms strategy to note that the POC radio controller should only be used to show how to use existing controllers into a single radio controller. It is not prescriptive of an exception to where the AIRIA role should be. Radio should not have the role on the host but instead use a native radio
inputin the shadow DOM if possible (or an element with aradiorole in the shadow).Motivation and context
The 2nd-gen migration is an opportunity to address known accessibility gaps, align with the latest WAI-ARIA Authoring Practices, and ensure the component meets WCAG 2.2 AA compliance. This is the accessibility-recommendations step (SWC-2349) of the Radio epic (SWC-2348), and its findings feed directly into the migration plan (SWC-2350), the RadioGroupController research spike (SWC-2470), and the forms strategy RFC's open questions on grouped selection.
Related issue(s)
Screenshots (if appropriate)
Author's checklist
Reviewer's checklist
patch,minor, ormajorfeaturesManual review test cases
Review the radio and radio group accessibility migration analyses
Review the changes to forms-strategy-rfc.md