From 33c7ca44fcbb46574300513bee60f7e49c201c0c Mon Sep 17 00:00:00 2001 From: Stephanie Hobson Date: Thu, 10 Sep 2026 13:20:12 -0700 Subject: [PATCH] Standardize component focus states on :focus-visible (#1084) 29 bare :focus selectors across 12 component partials, converted to :focus-visible so the focus ring only shows for keyboard/assistive-tech focus rather than every mouse click. Left the single pre-existing :focus-within in _menu.scss alone -- different pseudo-class, not in scope. Breadcrumb, Button, Card, Footer, Menu, Menu Item, Menu List, Modal, Navigation, Notification Bar, Sidebar Menu, Sticky Promo Mechanical rename -- all 29 occurrences were the simple &:focus { or &:focus, shape, no ambiguity with other selectors. Verified with a targeted grep across the 12 files before and after (0 bare :focus remaining, 29 :focus-visible present) and a direct sass compile. Form text inputs/textareas are a separate, deliberately excluded case (D2, not this commit) -- :focus-visible doesn't reliably match mouse-focused text fields across engines, so losing the ring on click there would be a real accessibility regression, not an improvement. Stacked on v23/desc-to-body (branched here per the plan's documented fallback, since that stack hasn't merged yet -- will rebase onto main once it does). Part of #1084. Verified: npm run lint, npm test (47 specs, Firefox + Chrome), and a direct sass --verbose compile confirming 18 :focus-visible selectors in the compiled protocol-components.css with no new warnings. --- CHANGELOG.md | 3 +++ assets/sass/protocol/components/_breadcrumb.scss | 2 +- assets/sass/protocol/components/_button.scss | 4 ++-- assets/sass/protocol/components/_card.scss | 4 ++-- assets/sass/protocol/components/_footer.scss | 10 +++++----- assets/sass/protocol/components/_menu-item.scss | 4 ++-- assets/sass/protocol/components/_menu-list.scss | 4 ++-- assets/sass/protocol/components/_menu.scss | 6 +++--- assets/sass/protocol/components/_modal.scss | 4 ++-- assets/sass/protocol/components/_navigation.scss | 2 +- .../protocol/components/_notification-bar.scss | 14 +++++++------- assets/sass/protocol/components/_sidebar-menu.scss | 2 +- assets/sass/protocol/components/_sticky-promo.scss | 2 +- 13 files changed, 32 insertions(+), 29 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e9136f6..75ad80c0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -49,6 +49,9 @@ Introducing theme variables! CSS variables beginning with `--theme-` will adjust * Added status color variables (`--theme-color-success-*`, `--theme-color-error-*`, `--theme-color-warning-*`, `--theme-color-info-*`) * Removed Sass color variables from `_themes-sass.scss` (use CSS variables instead) +### Accessibility + +* (breaking) Standardized interactive component states on `:focus-visible` instead of `:focus`. ## Component changes ### Feature Card diff --git a/assets/sass/protocol/components/_breadcrumb.scss b/assets/sass/protocol/components/_breadcrumb.scss index 4d084605..cbf1dd4f 100644 --- a/assets/sass/protocol/components/_breadcrumb.scss +++ b/assets/sass/protocol/components/_breadcrumb.scss @@ -39,7 +39,7 @@ &:hover, &:active, - &:focus { + &:focus-visible { text-decoration: none; } } diff --git a/assets/sass/protocol/components/_button.scss b/assets/sass/protocol/components/_button.scss index ff9604cd..1d7e7caa 100644 --- a/assets/sass/protocol/components/_button.scss +++ b/assets/sass/protocol/components/_button.scss @@ -31,7 +31,7 @@ box-shadow: none; } - &:focus { + &:focus-visible { border-color: var(--theme-button-border-color-focus); box-shadow: var(--theme-field-focus-ring); outline-offset: 1px; @@ -213,7 +213,7 @@ &:hover, &:active, - &:focus { + &:focus-visible { text-decoration: underline; } } diff --git a/assets/sass/protocol/components/_card.scss b/assets/sass/protocol/components/_card.scss index 5d86e0c5..71f1e274 100644 --- a/assets/sass/protocol/components/_card.scss +++ b/assets/sass/protocol/components/_card.scss @@ -102,7 +102,7 @@ &:hover, &:active, - &:focus { + &:focus-visible { transition: box-shadow 0.1s ease-in-out; box-shadow: 0 0 0 4px $color-marketing-gray-20; @@ -143,7 +143,7 @@ &:hover, &:active, - &:focus { + &:focus-visible { box-shadow: 0 0 0 4px $color-marketing-gray-80; } diff --git a/assets/sass/protocol/components/_footer.scss b/assets/sass/protocol/components/_footer.scss index 78538cc6..871e4c5a 100644 --- a/assets/sass/protocol/components/_footer.scss +++ b/assets/sass/protocol/components/_footer.scss @@ -27,7 +27,7 @@ text-decoration: underline; &:hover, - &:focus, + &:focus-visible, &:active { text-decoration: none; } @@ -93,7 +93,7 @@ text-decoration: none; &:hover, - &:focus, + &:focus-visible, &:active { text-decoration: underline; } @@ -171,7 +171,7 @@ padding: 0; border-bottom: 0; - button:focus { + button:focus-visible { outline: 1px dotted #fff; } @@ -240,7 +240,7 @@ color: tokens.$color-light-gray-50; &:hover, - &:focus, + &:focus-visible, &:active { text-decoration: none; } @@ -291,7 +291,7 @@ } &:hover, - &:focus { + &:focus-visible { border-bottom-color: var(--theme-body-text-color-inverse); } } diff --git a/assets/sass/protocol/components/_menu-item.scss b/assets/sass/protocol/components/_menu-item.scss index 3a6cbbde..671972ef 100644 --- a/assets/sass/protocol/components/_menu-item.scss +++ b/assets/sass/protocol/components/_menu-item.scss @@ -17,7 +17,7 @@ $icon-size: 24px; &:hover, &:active, - &:focus { + &:focus-visible { .mzp-c-menu-item-title { transition: border-bottom-color 100ms ease-in-out; border-bottom: 2px solid var(--theme-body-text-color); @@ -82,7 +82,7 @@ $icon-size: 24px; &:hover, &:active, - &:focus { + &:focus-visible { color: var(--theme-body-text-color); text-decoration: underline; } diff --git a/assets/sass/protocol/components/_menu-list.scss b/assets/sass/protocol/components/_menu-list.scss index e4a7f3b2..7d0b9321 100644 --- a/assets/sass/protocol/components/_menu-list.scss +++ b/assets/sass/protocol/components/_menu-list.scss @@ -46,7 +46,7 @@ } &:hover, - &:focus { + &:focus-visible { background: $color-marketing-gray-20; color: var(--theme-link-color-hover); @@ -99,7 +99,7 @@ } &:hover, - &:focus { + &:focus-visible { color: var(--theme-link-color-hover); text-decoration: none; diff --git a/assets/sass/protocol/components/_menu.scss b/assets/sass/protocol/components/_menu.scss index 7af4bd7a..f80fcf6a 100644 --- a/assets/sass/protocol/components/_menu.scss +++ b/assets/sass/protocol/components/_menu.scss @@ -60,7 +60,7 @@ &:hover, &:active, - &:focus { + &:focus-visible { color: inherit; text-decoration: underline; } @@ -137,7 +137,7 @@ } &.mzp-has-drop-down:hover, - &.mzp-has-drop-down:focus { + &.mzp-has-drop-down:focus-visible { .mzp-c-menu-title { @include highlighted; z-index: 1001; @@ -225,7 +225,7 @@ width: 16px; &:hover, - &:focus { + &:focus-visible { transform: scale(1.2); } diff --git a/assets/sass/protocol/components/_modal.scss b/assets/sass/protocol/components/_modal.scss index c4043f5e..03ded356 100644 --- a/assets/sass/protocol/components/_modal.scss +++ b/assets/sass/protocol/components/_modal.scss @@ -88,12 +88,12 @@ html.mzp-is-noscroll { cursor: pointer; &:hover, - &:focus { + &:focus-visible { transition: transform 0.1s ease-in-out; transform: scale(1.1); } - &:focus { + &:focus-visible { outline: 1px dotted var(--theme-body-text-color-inverse); } } diff --git a/assets/sass/protocol/components/_navigation.scss b/assets/sass/protocol/components/_navigation.scss index 77242dd6..85707311 100644 --- a/assets/sass/protocol/components/_navigation.scss +++ b/assets/sass/protocol/components/_navigation.scss @@ -200,7 +200,7 @@ &:hover, &:active, - &:focus, + &:focus-visible, &.mzp-is-active { background-color: $color-marketing-gray-20; } diff --git a/assets/sass/protocol/components/_notification-bar.scss b/assets/sass/protocol/components/_notification-bar.scss index fea30cff..b96ff7ee 100644 --- a/assets/sass/protocol/components/_notification-bar.scss +++ b/assets/sass/protocol/components/_notification-bar.scss @@ -37,7 +37,7 @@ &:hover, &:active, - &:focus { + &:focus-visible { color: inherit; text-decoration: none; } @@ -76,7 +76,7 @@ cursor: pointer; } - &:focus { + &:focus-visible { border: 1px solid; } @@ -91,7 +91,7 @@ )); &:hover, - &:focus { + &:focus-visible { background-color: $color-marketing-gray-50; border: 0; } @@ -106,7 +106,7 @@ background-color: var(--theme-color-success-hover); &:hover, - &:focus { + &:focus-visible { background-color: var(--theme-color-success-active); } } @@ -121,7 +121,7 @@ background-color: var(--theme-color-error-hover); &:hover, - &:focus { + &:focus-visible { background-color: var(--theme-color-error-active); } } @@ -136,7 +136,7 @@ background-color: var(--theme-color-warning-hover); &:hover, - &:focus { + &:focus-visible { background-color: var(--theme-color-warning-active); } } @@ -155,7 +155,7 @@ background-color: var(--theme-color-info-hover); &:hover, - &:focus { + &:focus-visible { background-color: var(--theme-color-info-active); } } diff --git a/assets/sass/protocol/components/_sidebar-menu.scss b/assets/sass/protocol/components/_sidebar-menu.scss index 1af31260..37b8d196 100644 --- a/assets/sass/protocol/components/_sidebar-menu.scss +++ b/assets/sass/protocol/components/_sidebar-menu.scss @@ -64,7 +64,7 @@ transition: background-color 100ms ease-in; &:hover, - &:focus, + &:focus-visible, &:active { background-color: rgb(0, 0, 0, 0.05); text-decoration: underline; diff --git a/assets/sass/protocol/components/_sticky-promo.scss b/assets/sass/protocol/components/_sticky-promo.scss index cad61663..c6f9d892 100644 --- a/assets/sass/protocol/components/_sticky-promo.scss +++ b/assets/sass/protocol/components/_sticky-promo.scss @@ -108,7 +108,7 @@ $logos: ( )); &:hover, - &:focus { + &:focus-visible { cursor: pointer; }