A11y: shared toggle no longer emits dangling aria-labelledby (aria_id_unique) - #3374
Conversation
The shared toggle component (classes/views/shared/toggle.php) always
rendered aria-labelledby="{id}_label" on its switch span, but 6 callers
pass aria-label-attr instead of rendering a companion <label id="{id}_label">
element, leaving aria-labelledby pointing at an id that doesn't exist
anywhere in the document. Only emit aria-labelledby when aria-label-attr
isn't set; the two are mutually exclusive ways of naming the same control,
and aria-labelledby wins the accessible-name computation when both are
present, so the dangling attribute was never doing anything but failing
IBM Equal Access's aria_id_unique check.
Confirmed via a live Cypress + cy.checkIbmAccessibility run against all 12
admin pages admin-a11y.cy.js covers: 4 aria_id_unique hits before (Add-Ons
page's Stripe/Square/PayPal Commerce toggles, Styles page's Custom CSS
toggle), 0 after, matching formidable-pro#6696's "flagged 4 times" count.
|
Important Review skippedBot user detected. To trigger a single review, invoke the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Advanced Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
|
Overall Grade |
Security Reliability Complexity Hygiene |
Code Review Summary
| Analyzer | Status | Updated (UTC) | Details |
|---|---|---|---|
| PHP | Sep 18, 2026 1:57p.m. | Review ↗ | |
| JavaScript | Sep 18, 2026 1:57p.m. | Review ↗ |
Important
AI Review is run only on demand for your team. We're only showing results of static analysis review right now. To trigger AI Review, comment @deepsourcebot review on this thread.
|
Requested by: @Crabcyborg Build the toggle's aria attributes as an array and render them via $aria_attrs = array();
if ( ! empty( $args['aria-label-attr'] ) ) {
$aria_attrs['aria-label'] = $args['aria-label-attr'];
} else {
$aria_attrs['aria-labelledby'] = $id . '_label';
}
$aria_attrs['aria-checked'] = $aria_checked;Then in the markup: Keep the aria-label/aria-labelledby exclusivity in the array-building step, not the render — array_to_html_params() echoes every key it's handed, so if both ended up in the array it would reintroduce the dangling aria-labelledby this PR exists to fix. |
Requested by @Crabcyborg: replace the inline if/else PHP blocks with an array built once and rendered through FrmAppHelper::array_to_html_params(), keeping the aria-label/aria-labelledby exclusivity in the array-building step so array_to_html_params() never gets handed both.
|
Pushed 6c4532d: built the aria attrs as an array (exclusivity kept in the array-building step) and rendered via array_to_html_params(), per the request above. |
What was broken
classes/views/shared/toggle.php's switch span always renderedaria-labelledby="{$id}_label"unconditionally, alongsidearia-labelwhen$args['aria-label-attr']was passed. Six call sites passaria-label-attrbut never render a companion<label id="{$id}_label">element, soaria-labelledbypointed at an id that doesn't exist anywhere in the document — IBM Equal Access'saria_id_uniquerule (referenced by Strategy11/formidable-pro#6696).Confirmed live via
cy.checkIbmAccessibilityacross all 12 pagestests/cypress/e2e/admin-a11y.cy.jscovers: exactly 4 hits, matching the issue's "flagged 4 times" — the Add-Ons page's Stripe/Square/PayPal Commerce toggles and the Styles page's Custom CSS toggle, all rendered viaclasses/views/addons/addon.phpandclasses/views/styles/_quick-settings.phprespectively. Two more latent instances (classes/views/styles/_field-colors.php:188,classes/views/styles/_buttons.php:259) share the exact same pattern and are fixed by the same change, though they weren't reachable by the 12 scanned pages.What changed
aria-label-attrand the implicitaria-labelledby="{$id}_label"are now mutually exclusive — only emitaria-labelledbywhen noaria-label-attrwas given.aria-labelledbywins the accessible-name computation overaria-labelwhen both are present, so the old unconditionalaria-labelledbynever contributed anything for anaria-label-attrcaller except failing this rule.The one caller that relies on the
elsebranch (classes/views/frm-form-actions/_email_attachment_upsell.php, which renders a real<label id="{$id}_label" for="{$id}">and passes noaria-label-attr) gets byte-identical output to before — verified by inspection, not a live scan, since that view isn't one of the 12 admin pagesadmin-a11y.cy.jscovers.Markup-only, no CSS/JS change.
How it was verified
Live Cypress run (
npx cypress run --spec tests/cypress/e2e/admin-a11y.cy.js, plus a scratch spec filteringreport.resultstoruleId === 'aria_id_unique'for full snippet/path detail) against a real localwp-envsite: 4aria_id_uniquehits before this change, 0 after, across all 12 admin pages../vendor/bin/phpcsclean on the changed file. Self-reviewed (correctness, escaping, style-consistency, reuse) against this diff.Note on #3368
An existing open PR (#3368, also
Closes formidable-pro#6696) dedupes plainidattributes reused between the style editor's advanced-settings accordion and its quick-settings panel. Those ids aren't referenced by anyaria-*property, so that fix addresses a different (if real) duplicate-id concern, notaria_id_uniquespecifically — which only fires when an ARIA property references an invalid/missing/non-unique id. This PR's 4 confirmed hits are unrelated to #3368's 4 renamed ids. Not closing #6696 here since #3368 already claims it — leaving it to a human to decide whether #3368 stands on its own or gets retargeted at whatever it actually fixes.