From 690a50c1bffe631631068fa3653bde5a91e1d6e6 Mon Sep 17 00:00:00 2001 From: Khokan Sardar Date: Thu, 21 May 2026 00:18:55 +0530 Subject: [PATCH 1/4] Admin Menu: Fix multiple active menu highlights on mobile. Use :focus-visible for admin menu focus indicators so touch taps do not leave a stale active appearance on previously selected items. Clear the responsive selected state when another top-level menu is opened or when navigating via a submenu link. Limit admin menu view transitions to the current menu item to avoid overlapping active styles during navigation. Fixes #65280. --- src/js/_enqueues/admin/common.js | 28 ++++++++++++++++--- src/wp-admin/css/admin-menu.css | 21 ++++++++++---- src/wp-admin/css/view-transitions.css | 3 +- .../wpEnqueueViewTransitionsAdminCss.php | 1 + 4 files changed, 42 insertions(+), 11 deletions(-) diff --git a/src/js/_enqueues/admin/common.js b/src/js/_enqueues/admin/common.js index 2a7daba0d2dc4..1424642c4640a 100644 --- a/src/js/_enqueues/admin/common.js +++ b/src/js/_enqueues/admin/common.js @@ -1827,16 +1827,34 @@ $( function() { // Add menu events. $adminmenu.on( 'click.wp-responsive', 'li.wp-has-submenu > a', function( event ) { + var $link, $menuItem, state; + if ( ! $adminmenu.data('wp-responsive') ) { return; } - let state = ( 'false' === $( this ).attr( 'aria-expanded' ) ) ? 'true' : 'false'; - $( this ).parent( 'li' ).toggleClass( 'selected' ); - $( this ).attr( 'aria-expanded', state ); - $( this ).trigger( 'focus' ); + + $link = $( this ); + $menuItem = $link.parent( 'li' ); + state = ( 'false' === $link.attr( 'aria-expanded' ) ) ? 'true' : 'false'; + + $adminmenu.find( 'li.selected' ).not( $menuItem ).removeClass( 'selected' ) + .children( 'a' ).attr( 'aria-expanded', 'false' ); + + $menuItem.toggleClass( 'selected' ); + $link.attr( 'aria-expanded', state ); + $link.trigger( 'focus' ); event.preventDefault(); }); + $adminmenu.on( 'click.wp-responsive', '.wp-submenu a', function() { + if ( ! $adminmenu.data( 'wp-responsive' ) ) { + return; + } + + $adminmenu.find( 'li.selected' ).removeClass( 'selected' ) + .children( 'a' ).attr( 'aria-expanded', 'false' ); + }); + self.trigger(); $document.on( 'wp-window-resized.wp-responsive', this.trigger.bind( this ) ); @@ -1886,6 +1904,8 @@ $( function() { } $adminmenu.data( 'wp-responsive', 1 ); + $adminmenu.find( 'li.selected' ).not( '.wp-has-current-submenu' ).removeClass( 'selected' ) + .children( 'a' ).attr( 'aria-expanded', 'false' ); this.disableSortables(); }, diff --git a/src/wp-admin/css/admin-menu.css b/src/wp-admin/css/admin-menu.css index c4b32ac4b9e87..3e539f182ae25 100644 --- a/src/wp-admin/css/admin-menu.css +++ b/src/wp-admin/css/admin-menu.css @@ -101,20 +101,24 @@ } #adminmenu a:hover, -#adminmenu li.menu-top > a:focus, +#adminmenu li.menu-top > a:focus-visible, #adminmenu .wp-submenu a:hover, -#adminmenu .wp-submenu a:focus { +#adminmenu .wp-submenu a:focus-visible { color: #72aee6; } #adminmenu a:hover, -#adminmenu a:focus, +#adminmenu a:focus-visible, .folded #adminmenu .wp-submenu-head:hover { box-shadow: inset 4px 0 0 0 currentColor; transition: box-shadow .1s linear; border-radius: 0; } +#adminmenu li.wp-not-current-submenu > a.menu-top:focus:not(:focus-visible) { + box-shadow: none; +} + #adminmenu li.menu-top { border: none; min-height: 34px; @@ -167,15 +171,20 @@ /* ensure that wp-submenu's box shadow doesn't appear on top of the focused menu item's background. */ #adminmenu li.menu-top:hover, #adminmenu li.opensub > a.menu-top, -#adminmenu li > a.menu-top:focus { +#adminmenu li > a.menu-top:focus-visible { position: relative; background-color: #1d2327; color: #72aee6; } +#adminmenu li.wp-not-current-submenu > a.menu-top:focus:not(:focus-visible) { + background-color: transparent; + color: #f0f0f1; +} + .folded #adminmenu li.menu-top:hover, .folded #adminmenu li.opensub > a.menu-top, -.folded #adminmenu li > a.menu-top:focus { +.folded #adminmenu li > a.menu-top:focus-visible { z-index: 10000; } @@ -641,7 +650,7 @@ li#wp-admin-bar-menu-toggle { .auto-fold #adminmenu li.menu-top:hover, .auto-fold #adminmenu li.opensub > a.menu-top, - .auto-fold #adminmenu li > a.menu-top:focus { + .auto-fold #adminmenu li > a.menu-top:focus-visible { z-index: 10000; } diff --git a/src/wp-admin/css/view-transitions.css b/src/wp-admin/css/view-transitions.css index 538a90b502f9e..d67055ba12336 100644 --- a/src/wp-admin/css/view-transitions.css +++ b/src/wp-admin/css/view-transitions.css @@ -3,7 +3,8 @@ navigation: auto; } - #adminmenu > .menu-top { + #adminmenu > .menu-top.wp-has-current-submenu, + #adminmenu > .menu-top.current { view-transition-name: attr(id type(), none); } } diff --git a/tests/phpunit/tests/view-transitions/wpEnqueueViewTransitionsAdminCss.php b/tests/phpunit/tests/view-transitions/wpEnqueueViewTransitionsAdminCss.php index 0e977e69606f9..e02926ce9b29e 100644 --- a/tests/phpunit/tests/view-transitions/wpEnqueueViewTransitionsAdminCss.php +++ b/tests/phpunit/tests/view-transitions/wpEnqueueViewTransitionsAdminCss.php @@ -49,6 +49,7 @@ public function test_inline_css_included() { $this->assertIsArray( $after_data, 'Expected `after` data to be an array.' ); $css = wp_get_view_transitions_admin_css(); $this->assertStringContainsString( '@view-transition', $css ); + $this->assertStringContainsString( '.menu-top.wp-has-current-submenu', $css ); $this->assertContains( $css, $after_data ); } From 792cbd40bd2cf70040484ecec900c4619b9611e6 Mon Sep 17 00:00:00 2001 From: Khokan Sardar Date: Tue, 30 Jun 2026 16:28:53 +0530 Subject: [PATCH 2/4] Admin Menu: Focus the current item when opening the responsive menu. When the responsive menu is opened, focus was moved to the first menu item (Dashboard). On browsers that match :focus-visible for scripted focus (e.g. Safari/WebKit on iOS), that first item receives the active highlight, so two menu items appear active at once. Move focus to the current top-level item when one exists, falling back to the first menu link, so the focused item is the one that is meant to be highlighted. Follow-up to the :focus-visible changes for #65280. --- src/js/_enqueues/admin/common.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/js/_enqueues/admin/common.js b/src/js/_enqueues/admin/common.js index 1424642c4640a..56d6b6820934e 100644 --- a/src/js/_enqueues/admin/common.js +++ b/src/js/_enqueues/admin/common.js @@ -1779,7 +1779,15 @@ $( function() { $wpwrap.toggleClass( 'wp-responsive-open' ); if ( $wpwrap.hasClass( 'wp-responsive-open' ) ) { $(this).find('a').attr( 'aria-expanded', 'true' ); - $( '#adminmenu a:first' ).trigger( 'focus' ); + + // Move focus to the current menu item when one exists, so browsers that + // match :focus-visible for scripted focus do not show a stale active + // highlight on the first item. Fall back to the first menu link. + var $focusTarget = $adminmenu.find( 'li.wp-has-current-submenu > a.menu-top, li.current > a.menu-top' ).first(); + if ( ! $focusTarget.length ) { + $focusTarget = $adminmenu.find( 'a' ).first(); + } + $focusTarget.trigger( 'focus' ); } else { $(this).find('a').attr( 'aria-expanded', 'false' ); } From c6230b9528139f3afec16e8c95444f2a3d02aa84 Mon Sep 17 00:00:00 2001 From: Khokan Sardar Date: Tue, 30 Jun 2026 16:53:08 +0530 Subject: [PATCH 3/4] Admin Menu: Use :focus-visible for the color scheme menu highlight. The admin color schemes (compiled from colors/_admin.scss) styled the top-level menu highlight on plain :focus, overriding the :focus-visible rules in admin-menu.css since color scheme CSS loads later. This brought back the active appearance for pointer/scripted focus on browsers that match :focus-visible inconsistently. Switch the color scheme highlight to :focus-visible to match admin-menu.css, and drop the now-redundant :not(:focus-visible) overrides that were compensating for the mismatch. Follow-up to the :focus-visible changes for #65280. --- src/wp-admin/css/admin-menu.css | 9 --------- src/wp-admin/css/colors/_admin.scss | 2 +- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/src/wp-admin/css/admin-menu.css b/src/wp-admin/css/admin-menu.css index 3e539f182ae25..2391372015bac 100644 --- a/src/wp-admin/css/admin-menu.css +++ b/src/wp-admin/css/admin-menu.css @@ -115,10 +115,6 @@ border-radius: 0; } -#adminmenu li.wp-not-current-submenu > a.menu-top:focus:not(:focus-visible) { - box-shadow: none; -} - #adminmenu li.menu-top { border: none; min-height: 34px; @@ -177,11 +173,6 @@ color: #72aee6; } -#adminmenu li.wp-not-current-submenu > a.menu-top:focus:not(:focus-visible) { - background-color: transparent; - color: #f0f0f1; -} - .folded #adminmenu li.menu-top:hover, .folded #adminmenu li.opensub > a.menu-top, .folded #adminmenu li > a.menu-top:focus-visible { diff --git a/src/wp-admin/css/colors/_admin.scss b/src/wp-admin/css/colors/_admin.scss index 313666e3ded73..97118b6e8dc16 100644 --- a/src/wp-admin/css/colors/_admin.scss +++ b/src/wp-admin/css/colors/_admin.scss @@ -266,7 +266,7 @@ input[type="radio"]:focus { #adminmenu a:hover, #adminmenu li.menu-top:hover, #adminmenu li.opensub > a.menu-top, -#adminmenu li > a.menu-top:focus { +#adminmenu li > a.menu-top:focus-visible { color: variables.$menu-highlight-text; background-color: variables.$menu-highlight-background; } From 4c09b2eb0614ea708875f5c16f8ec3d8d5c0a8c9 Mon Sep 17 00:00:00 2001 From: Khokan Sardar Date: Fri, 31 Jul 2026 08:30:47 +0530 Subject: [PATCH 4/4] Administration: Keep the responsive menu's initial focus on the first menu link. Moving focus to the current menu item when the responsive menu is opened is a behavior change beyond the scope of this ticket, and is better considered as a separate enhancement. See #65280. --- src/js/_enqueues/admin/common.js | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/js/_enqueues/admin/common.js b/src/js/_enqueues/admin/common.js index ef93eec431812..68fd58690e42e 100644 --- a/src/js/_enqueues/admin/common.js +++ b/src/js/_enqueues/admin/common.js @@ -1779,15 +1779,7 @@ $( function() { $wpwrap.toggleClass( 'wp-responsive-open' ); if ( $wpwrap.hasClass( 'wp-responsive-open' ) ) { $(this).find('a').attr( 'aria-expanded', 'true' ); - - // Move focus to the current menu item when one exists, so browsers that - // match :focus-visible for scripted focus do not show a stale active - // highlight on the first item. Fall back to the first menu link. - var $focusTarget = $adminmenu.find( 'li.wp-has-current-submenu > a.menu-top, li.current > a.menu-top' ).first(); - if ( ! $focusTarget.length ) { - $focusTarget = $adminmenu.find( 'a' ).first(); - } - $focusTarget.trigger( 'focus' ); + $( '#adminmenu a:first' ).trigger( 'focus' ); } else { $(this).find('a').attr( 'aria-expanded', 'false' ); }