Repository navigation
Improve consent banner/modal accessibility (WCAG 2.1 AA) - #9
Merged
Merged
Conversation
…ocus - Add an always-present, visually hidden aria-live="polite" status region (render_status_announcer()) so screen reader users hear the banner appearing and later consent-decision changes (accepted/ rejected), which a purely visual class toggle never conveyed. - Associate the banner's message text via aria-describedby/id so its accessible description isn't just a generic "region"/"dialog" role. - In the popup (true modal) layout, treat Escape as an explicit reject decision (restoring focus to the triggering element and unbinding the keydown handler), instead of only trapping Tab — consistent with Escape needing to close a modal without leaving it as a dead end that never records a decision. - Extend the existing focus trap to also cover input/[tabindex] elements, not just links/buttons. - Cover the reduced-motion media query for button/reopen hover and focus transitions, not just the banner's own reveal transition. Refs #2. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
davidperezgar
approved these changes
Sep 29, 2026
davidperezgar
approved these changes
Sep 29, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes #2. Audited and improved the existing cookie consent banner/popup markup and JS against the issue's "Areas to cover" checklist. Much of the ground work (native
<button>elements,role="dialog"/aria-modalon the popup layout vs.role="region"on the bar/box layouts, a Tab focus trap, focus restore to the triggering element,:focus-visiblestyles, algorithmic WCAG-AA contrast enforcement for customizable colors, and aprefers-reduced-motionrule) was already in place before this PR. This PR fills the remaining gaps:role="status" aria-live="polite"live region (CookieNotice::render_status_announcer()) that JS fills in to announce the banner appearing and later consent-decision changes ("Cookies accepted."/"Cookies rejected."). Strings are localized viawp_localize_script(frcnCookieNoticeA11y).aria-describedbypointing at the message paragraph's ownid, so assistive technology has more than just a generic "region"/"dialog" announcement.input/[tabindex]elements, not just links/buttons.prefers-reduced-motion: reducerule to also cover the accept/reject buttons' and reopen trigger's hover/active/focus transitions, not just the banner's own reveal transition.Acceptance criteria (from #2)
<button>s; popup Tab-traps and Escape provides an explicit way out.aria-label/aria-describedbyon the banner,aria-hiddenon the decorative icon. (No custom toggle/checkbox exists in this free plugin's scope yet — that's a PRO/add-on "Customize" feature — so noaria-checkedwork was needed here.)CookieNotice::get_readable_text_color()/get_readable_on_white_color(), which algorithmically derive a WCAG-AA-safe text/link color for any configured accent/background pair (verified by the pre-existingCookieNoticeContrastTest).Test plan
tests/Unit/CookieNoticeAccessibilityTest.php(PHPUnit): assertsrole="region"vsrole="dialog"/aria-modal="true"per layout,aria-label/aria-describedby, native<button>markup, the decorative icon'saria-hidden, and that the new status-announcer live region is always present (including on the configured policy page).tests/js/cookie-notice-focus-management.test.js(Node's built-in test runner, matching the existingcookie-notice-injection.test.jsstyle/DOM-stub approach): covers opening the popup moving focus to the accept button and announcing the banner, Tab/Shift+Tab wrapping at the first/last focusable element (and not wrapping in the middle), Escape rejecting + announcing + restoring focus + unbinding the handler, and accepting via the button also restoring focus and unbinding the handler.composer lintandcomposer phpstan: both pass clean onincludes/(the only files those gates cover).node --test tests/js/*.test.js: all 26 tests pass (20 pre-existing + 6 new).composer test(PHPUnit): could not be run in this environment — there's no MySQL/MariaDB available to stand up the WordPress test suite (bin/install-wp-tests.shrequires it), and this is a pre-existing limitation of the sandbox, not something introduced by this PR — the same failure reproduces on the pre-existingCookieNoticeContrastTestwith no changes applied. The new test file was verified withphp -land follows the exact patterns (Yoast\WPTestUtils\WPIntegration\TestCase,render_banner()+ob_start()) of the existing, presumably-passing-in-CI test suite. Recommend confirming it passes in CI/a real dev environment before merging.