Skip to content

A11y: shared toggle no longer emits dangling aria-labelledby (aria_id_unique) - #3374

Merged
Crabcyborg merged 2 commits into
masterfrom
fix/issue-6696-toggle-aria-labelledby
Sep 18, 2026
Merged

Crabcyborg merged 2 commits into
masterfrom
fix/issue-6696-toggle-aria-labelledby

Conversation

@vivi-the-going-merry

Copy link
Copy Markdown
Contributor

What was broken

classes/views/shared/toggle.php's switch span always rendered aria-labelledby="{$id}_label" unconditionally, alongside aria-label when $args['aria-label-attr'] was passed. Six call sites pass aria-label-attr but never render a companion <label id="{$id}_label"> element, so aria-labelledby pointed at an id that doesn't exist anywhere in the document — IBM Equal Access's aria_id_unique rule (referenced by Strategy11/formidable-pro#6696).

Confirmed live via cy.checkIbmAccessibility across all 12 pages tests/cypress/e2e/admin-a11y.cy.js covers: 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 via classes/views/addons/addon.php and classes/views/styles/_quick-settings.php respectively. 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-attr and the implicit aria-labelledby="{$id}_label" are now mutually exclusive — only emit aria-labelledby when no aria-label-attr was given. aria-labelledby wins the accessible-name computation over aria-label when both are present, so the old unconditional aria-labelledby never contributed anything for an aria-label-attr caller except failing this rule.

The one caller that relies on the else branch (classes/views/frm-form-actions/_email_attachment_upsell.php, which renders a real <label id="{$id}_label" for="{$id}"> and passes no aria-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 pages admin-a11y.cy.js covers.

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 filtering report.results to ruleId === 'aria_id_unique' for full snippet/path detail) against a real local wp-env site: 4 aria_id_unique hits before this change, 0 after, across all 12 admin pages. ./vendor/bin/phpcs clean 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 plain id attributes reused between the style editor's advanced-settings accordion and its quick-settings panel. Those ids aren't referenced by any aria-* property, so that fix addresses a different (if real) duplicate-id concern, not aria_id_unique specifically — 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.

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.
@coderabbitai

coderabbitai Bot commented Sep 18, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Bot user detected.

To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Advanced

Run ID: 1fb870d5-3c12-4ffe-a033-0bb6c03eb762

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@deepsource-io

deepsource-io Bot commented Sep 18, 2026

Copy link
Copy Markdown

DeepSource Code Review

We reviewed changes in e404085...6c4532d on this pull request. Below is the summary for the review, and you can see the individual issues we found as inline review comments.

See full review on DeepSource ↗

PR Report Card

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.

@robin-the-going-merry

Copy link
Copy Markdown

Requested by: @Crabcyborg

Build the toggle's aria attributes as an array and render them via FrmAppHelper::array_to_html_params() instead of the inline if/else PHP blocks:

$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: <?php FrmAppHelper::array_to_html_params( $aria_attrs, true ); ?>.

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.

@vivi-the-going-merry vivi-the-going-merry Bot added vivi-working Vivi is actively working this and removed vivi-pickup labels Sep 18, 2026
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.
@vivi-the-going-merry vivi-the-going-merry Bot added franky-review and removed vivi-working Vivi is actively working this labels Sep 18, 2026
@vivi-the-going-merry

Copy link
Copy Markdown
Contributor Author

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.

@Crabcyborg Crabcyborg left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good!

🚀

@Crabcyborg Crabcyborg added this to the 6.36 milestone Sep 18, 2026
@Crabcyborg
Crabcyborg merged commit 6b8e650 into master Sep 18, 2026
31 checks passed
@Crabcyborg
Crabcyborg deleted the fix/issue-6696-toggle-aria-labelledby branch September 18, 2026 14:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant