From 6244184cb43d461dbbd6c6260a67fe7699d9e386 Mon Sep 17 00:00:00 2001 From: Sukhendu Sekhar Guria Date: Mon, 13 Apr 2026 16:35:40 +0530 Subject: [PATCH 01/17] Accessibility: Require Alt key for arrow navigation in theme and media modals --- src/js/_enqueues/wp/theme.js | 32 +++++++++++--------- src/js/media/views/frame/edit-attachments.js | 17 ++++++----- 2 files changed, 28 insertions(+), 21 deletions(-) diff --git a/src/js/_enqueues/wp/theme.js b/src/js/_enqueues/wp/theme.js index 56107ee475057..30efa1dfdc51a 100644 --- a/src/js/_enqueues/wp/theme.js +++ b/src/js/_enqueues/wp/theme.js @@ -909,7 +909,7 @@ themes.view.Preview = themes.view.Details.extend({ 'click .devices button': 'previewDevice', 'click .previous-theme': 'previousTheme', 'click .next-theme': 'nextTheme', - 'keyup': 'keyEvent', + 'keydown': 'keyEvent', 'click .theme-install': 'installTheme' }, @@ -1012,18 +1012,20 @@ themes.view.Preview = themes.view.Details.extend({ this.close(); } - // Return if Ctrl + Shift or Shift key pressed - if ( event.shiftKey || ( event.ctrlKey && event.shiftKey ) ) { + // Arrow key navigation requires Alt key to avoid interfering with screen reader navigation. + if ( ! event.altKey || event.repeat ) { return; } // The right arrow key, next theme. if ( event.keyCode === 39 ) { - _.once( this.nextTheme() ); + event.preventDefault(); + this.nextTheme(); } // The left arrow key, previous theme. if ( event.keyCode === 37 ) { + event.preventDefault(); this.previousTheme(); } }, @@ -1111,7 +1113,7 @@ themes.view.Themes = wp.Backbone.View.extend({ } ); // Bind keyboard events. - $( 'body' ).on( 'keyup', function( event ) { + $( 'body' ).on( 'keydown.wp-themes', function( event ) { if ( ! self.overlay ) { return; } @@ -1121,25 +1123,27 @@ themes.view.Themes = wp.Backbone.View.extend({ return; } - // Return if Ctrl + Shift or Shift key pressed - if ( event.shiftKey || ( event.ctrlKey && event.shiftKey ) ) { + // Pressing the escape key fires a theme:collapse event. + if ( event.keyCode === 27 ) { + self.overlay.collapse( event ); + } + + // Arrow key navigation requires Alt key to avoid interfering with screen reader navigation. + if ( ! event.altKey || event.repeat ) { return; } - // Pressing the right arrow key fires a theme:next event. + // Pressing Alt + right arrow key fires a theme:next event. if ( event.keyCode === 39 ) { + event.preventDefault(); self.overlay.nextTheme(); } - // Pressing the left arrow key fires a theme:previous event. + // Pressing Alt + left arrow key fires a theme:previous event. if ( event.keyCode === 37 ) { + event.preventDefault(); self.overlay.previousTheme(); } - - // Pressing the escape key fires a theme:collapse event. - if ( event.keyCode === 27 ) { - self.overlay.collapse( event ); - } }); }, diff --git a/src/js/media/views/frame/edit-attachments.js b/src/js/media/views/frame/edit-attachments.js index 250f1b5214665..8fb5372673e71 100644 --- a/src/js/media/views/frame/edit-attachments.js +++ b/src/js/media/views/frame/edit-attachments.js @@ -247,25 +247,28 @@ EditAttachments = MediaFrame.extend(/** @lends wp.media.view.MediaFrame.EditAtta return ( this.getCurrentIndex() - 1 ) > -1; }, /** - * Respond to the keyboard events: right arrow, left arrow, except when - * focus is in a textarea or input field. + * Respond to the keyboard events: Alt + right arrow, Alt + left arrow, + * except when focus is in an interactive field. Requires the Alt modifier + * key to avoid interfering with screen reader navigation. */ keyEvent: function( event ) { - if ( ( 'INPUT' === event.target.nodeName || 'TEXTAREA' === event.target.nodeName ) && ! event.target.disabled ) { + if ( ( 'INPUT' === event.target.nodeName || 'TEXTAREA' === event.target.nodeName || 'SELECT' === event.target.nodeName || 'BUTTON' === event.target.nodeName || 'A' === event.target.nodeName ) && ! event.target.disabled ) { return; } - // Return if Ctrl + Shift or Shift key pressed - if ( event.shiftKey || ( event.ctrlKey && event.shiftKey ) ) { + // Arrow key navigation requires Alt key to avoid interfering with screen reader navigation. + if ( ! event.altKey || event.repeat ) { return; } - // The right arrow key. + // Alt + right arrow key. if ( 39 === event.keyCode ) { + event.preventDefault(); this.nextMediaItem(); } - // The left arrow key. + // Alt + left arrow key. if ( 37 === event.keyCode ) { + event.preventDefault(); this.previousMediaItem(); } }, From 2e7344823ff9d318dd7950cccbe7e05deb988853 Mon Sep 17 00:00:00 2001 From: Sukhendu Sekhar Guria Date: Tue, 7 Jul 2026 18:42:48 +0530 Subject: [PATCH 02/17] fix(a11y): announce modal navigation changes --- src/js/_enqueues/wp/theme.js | 13 ++++++++++ src/js/media/views/frame/edit-attachments.js | 27 ++++++++++++++++---- src/wp-admin/theme-install.php | 2 ++ src/wp-admin/themes.php | 2 ++ src/wp-includes/media.php | 2 ++ 5 files changed, 41 insertions(+), 5 deletions(-) diff --git a/src/js/_enqueues/wp/theme.js b/src/js/_enqueues/wp/theme.js index 30efa1dfdc51a..4e6fe8e45fcdf 100644 --- a/src/js/_enqueues/wp/theme.js +++ b/src/js/_enqueues/wp/theme.js @@ -16,6 +16,15 @@ themes = wp.themes = wp.themes || {}; themes.data = _wpThemeSettings; l10n = themes.data.l10n; +// Announce the theme shown after previous/next navigation. +themes.announceTheme = function( model ) { + var name = model.get( 'name' ) || model.get( 'id' ); + + wp.a11y.speak( l10n.themeViewed.replace( '%s', function() { + return name; + } ) ); +}; + // Shortcut for isInstall check. themes.isInstall = !! themes.data.settings.isInstall; @@ -549,6 +558,7 @@ themes.view.Theme = wp.Backbone.View.extend({ preview.render(); this.setNavButtonsState(); $( '.next-theme' ).trigger( 'focus' ); + themes.announceTheme( self.current ); }) .listenTo( preview, 'theme:previous', function() { @@ -579,6 +589,7 @@ themes.view.Theme = wp.Backbone.View.extend({ preview.render(); this.setNavButtonsState(); $( '.previous-theme' ).trigger( 'focus' ); + themes.announceTheme( self.current ); }); this.listenTo( preview, 'preview:close', function() { @@ -1326,6 +1337,7 @@ themes.view.Themes = wp.Backbone.View.extend({ // Trigger a route update for the current model. self.theme.trigger( 'theme:expand', nextModel.cid ); + themes.announceTheme( nextModel ); } }, @@ -1353,6 +1365,7 @@ themes.view.Themes = wp.Backbone.View.extend({ // Trigger a route update for the current model. self.theme.trigger( 'theme:expand', previousModel.cid ); + themes.announceTheme( previousModel ); } }, diff --git a/src/js/media/views/frame/edit-attachments.js b/src/js/media/views/frame/edit-attachments.js index 8fb5372673e71..54ec5e591f046 100644 --- a/src/js/media/views/frame/edit-attachments.js +++ b/src/js/media/views/frame/edit-attachments.js @@ -1,5 +1,6 @@ var Frame = wp.media.view.Frame, MediaFrame = wp.media.view.MediaFrame, + l10n = wp.media.view.l10n, $ = jQuery, EditAttachments; @@ -202,26 +203,34 @@ EditAttachments = MediaFrame.extend(/** @lends wp.media.view.MediaFrame.EditAtta * Click handler to switch to the previous media item. */ previousMediaItem: function() { + var model; + if ( ! this.hasPrevious() ) { return; } - this.trigger( 'refresh', this.library.at( this.getCurrentIndex() - 1 ) ); + model = this.library.at( this.getCurrentIndex() - 1 ); + this.trigger( 'refresh', model ); // Move focus to the Previous button. When there are no more items, to the Next button. this.focusNavButton( this.hasPrevious() ? '.left' : '.right' ); + this.announceMediaItem( model ); }, /** * Click handler to switch to the next media item. */ nextMediaItem: function() { + var model; + if ( ! this.hasNext() ) { return; } - this.trigger( 'refresh', this.library.at( this.getCurrentIndex() + 1 ) ); + model = this.library.at( this.getCurrentIndex() + 1 ); + this.trigger( 'refresh', model ); // Move focus to the Next button. When there are no more items, to the Previous button. this.focusNavButton( this.hasNext() ? '.right' : '.left' ); + this.announceMediaItem( model ); }, /** @@ -235,6 +244,14 @@ EditAttachments = MediaFrame.extend(/** @lends wp.media.view.MediaFrame.EditAtta $( which ).trigger( 'focus' ); }, + announceMediaItem: function( model ) { + var title = model.get( 'title' ) || model.get( 'filename' ) || model.get( 'id' ); + + wp.a11y.speak( l10n.mediaItemViewed.replace( '%s', function() { + return title; + } ) ); + }, + getCurrentIndex: function() { return this.library.indexOf( this.model ); }, @@ -248,11 +265,11 @@ EditAttachments = MediaFrame.extend(/** @lends wp.media.view.MediaFrame.EditAtta }, /** * Respond to the keyboard events: Alt + right arrow, Alt + left arrow, - * except when focus is in an interactive field. Requires the Alt modifier - * key to avoid interfering with screen reader navigation. + * except when focus is in a form field. Requires the Alt modifier key to + * avoid interfering with screen reader navigation. */ keyEvent: function( event ) { - if ( ( 'INPUT' === event.target.nodeName || 'TEXTAREA' === event.target.nodeName || 'SELECT' === event.target.nodeName || 'BUTTON' === event.target.nodeName || 'A' === event.target.nodeName ) && ! event.target.disabled ) { + if ( ( 'INPUT' === event.target.nodeName || 'TEXTAREA' === event.target.nodeName || 'SELECT' === event.target.nodeName ) && ! event.target.disabled ) { return; } diff --git a/src/wp-admin/theme-install.php b/src/wp-admin/theme-install.php index e9d217c0f7525..cd851e39989d4 100644 --- a/src/wp-admin/theme-install.php +++ b/src/wp-admin/theme-install.php @@ -67,6 +67,8 @@ /* translators: %d: Number of themes. */ 'themesFound' => __( 'Number of Themes found: %d' ), 'noThemesFound' => __( 'No themes found. Try a different search.' ), + /* translators: %s: Theme name. */ + 'themeViewed' => __( 'Viewing theme: %s' ), 'collapseSidebar' => __( 'Collapse Sidebar' ), 'expandSidebar' => __( 'Expand Sidebar' ), /* translators: Hidden accessibility text. */ diff --git a/src/wp-admin/themes.php b/src/wp-admin/themes.php index ca9f52b2a164f..1f94c710e456d 100644 --- a/src/wp-admin/themes.php +++ b/src/wp-admin/themes.php @@ -236,6 +236,8 @@ /* translators: %d: Number of themes. */ 'themesFound' => __( 'Number of Themes found: %d' ), 'noThemesFound' => __( 'No themes found. Try a different search.' ), + /* translators: %s: Theme name. */ + 'themeViewed' => __( 'Viewing theme: %s' ), ), ) ); diff --git a/src/wp-includes/media.php b/src/wp-includes/media.php index 8f6ec1cef4e26..f69fae6039e01 100644 --- a/src/wp-includes/media.php +++ b/src/wp-includes/media.php @@ -5070,6 +5070,8 @@ function wp_enqueue_media( $args = array() ) { 'mediaFound' => __( 'Number of media items found: %d' ), 'noMedia' => __( 'No media items found.' ), 'noMediaTryNewSearch' => __( 'No media items found. Try a different search.' ), + /* translators: %s: Media item title or file name. */ + 'mediaItemViewed' => __( 'Viewing media item: %s' ), // Library Details. 'attachmentDetails' => __( 'Attachment details' ), From d852e3f8e325bfa520dab36f202fbc4cfe7608b5 Mon Sep 17 00:00:00 2001 From: Joe Dolson Date: Fri, 24 Jul 2026 11:01:23 -0500 Subject: [PATCH 03/17] Switch to originalEvent --- src/js/_enqueues/wp/theme.js | 4 ++-- src/js/media/views/frame/edit-attachments.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/js/_enqueues/wp/theme.js b/src/js/_enqueues/wp/theme.js index 4e6fe8e45fcdf..a1cd6bff65d3a 100644 --- a/src/js/_enqueues/wp/theme.js +++ b/src/js/_enqueues/wp/theme.js @@ -1024,7 +1024,7 @@ themes.view.Preview = themes.view.Details.extend({ } // Arrow key navigation requires Alt key to avoid interfering with screen reader navigation. - if ( ! event.altKey || event.repeat ) { + if ( ! event.altKey || ( event.originalEvent && event.originalEvent.repeat ) ) { return; } @@ -1140,7 +1140,7 @@ themes.view.Themes = wp.Backbone.View.extend({ } // Arrow key navigation requires Alt key to avoid interfering with screen reader navigation. - if ( ! event.altKey || event.repeat ) { + if ( ! event.altKey || ( event.originalEvent && event.originalEvent.repeat ) ) { return; } diff --git a/src/js/media/views/frame/edit-attachments.js b/src/js/media/views/frame/edit-attachments.js index 54ec5e591f046..37887cd25f479 100644 --- a/src/js/media/views/frame/edit-attachments.js +++ b/src/js/media/views/frame/edit-attachments.js @@ -274,7 +274,7 @@ EditAttachments = MediaFrame.extend(/** @lends wp.media.view.MediaFrame.EditAtta } // Arrow key navigation requires Alt key to avoid interfering with screen reader navigation. - if ( ! event.altKey || event.repeat ) { + if ( ! event.altKey || ( event.originalEvent && event.originalEvent.repeat ) ) { return; } From 35dd5bc64218b82821a0fa0a23ef5d5fd4a9f16f Mon Sep 17 00:00:00 2001 From: Joe Dolson Date: Fri, 24 Jul 2026 11:10:49 -0500 Subject: [PATCH 04/17] Update shortcut documentation --- src/wp-admin/upload.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wp-admin/upload.php b/src/wp-admin/upload.php index 1f42a287e4957..5ceee4c2208da 100644 --- a/src/wp-admin/upload.php +++ b/src/wp-admin/upload.php @@ -190,7 +190,7 @@ function () { 'title' => __( 'Attachment Details' ), 'content' => '

' . __( 'Clicking an item will display an Attachment Details dialog, which allows you to preview media and make quick edits. Any changes you make to the attachment details will be automatically saved.' ) . '

' . - '

' . __( 'Use the arrow buttons at the top of the dialog, or the left and right arrow keys on your keyboard, to navigate between media items quickly.' ) . '

' . + '

' . __( 'Use the arrow buttons at the top of the dialog, or alt/option plus the left or right arrow keys on your keyboard, to navigate between media items quickly.' ) . '

' . '

' . __( 'You can also delete individual items and access the extended edit screen from the details dialog.' ) . '

', ) ); From 7ed5d7a8034e76fa7c850d99a38654255e874308 Mon Sep 17 00:00:00 2001 From: Joe Dolson Date: Fri, 24 Jul 2026 12:39:13 -0500 Subject: [PATCH 05/17] Add support for alt key navigation in customizer in --- src/js/_enqueues/wp/customize/controls.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/js/_enqueues/wp/customize/controls.js b/src/js/_enqueues/wp/customize/controls.js index a5846d45f687c..3d2c1dc325422 100644 --- a/src/js/_enqueues/wp/customize/controls.js +++ b/src/js/_enqueues/wp/customize/controls.js @@ -1777,6 +1777,11 @@ return; } + // Require the alt key for arrow events. + if ( 27 !== event.keyCode && ( ! event.altKey || ( event.originalEvent && event.originalEvent.repeat ) ) ) { + return; + } + // Pressing the right arrow key fires a theme:next event. if ( 39 === event.keyCode ) { section.nextTheme(); From f0af40369aceb230e36c5b4488a7a9265ee8eb02 Mon Sep 17 00:00:00 2001 From: Joe Dolson Date: Sun, 26 Jul 2026 14:10:14 -0500 Subject: [PATCH 06/17] Remove event repetition gate to restore previous behavior --- src/js/_enqueues/wp/customize/controls.js | 2 +- src/js/_enqueues/wp/theme.js | 4 ++-- src/js/media/views/frame/edit-attachments.js | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/js/_enqueues/wp/customize/controls.js b/src/js/_enqueues/wp/customize/controls.js index 3d2c1dc325422..afcd3986ad3a5 100644 --- a/src/js/_enqueues/wp/customize/controls.js +++ b/src/js/_enqueues/wp/customize/controls.js @@ -1778,7 +1778,7 @@ } // Require the alt key for arrow events. - if ( 27 !== event.keyCode && ( ! event.altKey || ( event.originalEvent && event.originalEvent.repeat ) ) ) { + if ( 27 !== event.keyCode && ! event.altKey ) { return; } diff --git a/src/js/_enqueues/wp/theme.js b/src/js/_enqueues/wp/theme.js index a1cd6bff65d3a..ee2ca21f77def 100644 --- a/src/js/_enqueues/wp/theme.js +++ b/src/js/_enqueues/wp/theme.js @@ -1024,7 +1024,7 @@ themes.view.Preview = themes.view.Details.extend({ } // Arrow key navigation requires Alt key to avoid interfering with screen reader navigation. - if ( ! event.altKey || ( event.originalEvent && event.originalEvent.repeat ) ) { + if ( ! event.altKey ) { return; } @@ -1140,7 +1140,7 @@ themes.view.Themes = wp.Backbone.View.extend({ } // Arrow key navigation requires Alt key to avoid interfering with screen reader navigation. - if ( ! event.altKey || ( event.originalEvent && event.originalEvent.repeat ) ) { + if ( ! event.altKey ) { return; } diff --git a/src/js/media/views/frame/edit-attachments.js b/src/js/media/views/frame/edit-attachments.js index 37887cd25f479..bcaa8f562ab41 100644 --- a/src/js/media/views/frame/edit-attachments.js +++ b/src/js/media/views/frame/edit-attachments.js @@ -274,7 +274,7 @@ EditAttachments = MediaFrame.extend(/** @lends wp.media.view.MediaFrame.EditAtta } // Arrow key navigation requires Alt key to avoid interfering with screen reader navigation. - if ( ! event.altKey || ( event.originalEvent && event.originalEvent.repeat ) ) { + if ( ! event.altKey ) { return; } From 6a80e0245c22f5bb554d14a1bbd6cf15edf0891e Mon Sep 17 00:00:00 2001 From: Joe Dolson Date: Sun, 26 Jul 2026 14:11:55 -0500 Subject: [PATCH 07/17] Merge & simplify announce strings --- src/wp-admin/theme-install.php | 2 +- src/wp-admin/themes.php | 2 +- src/wp-includes/class-wp-customize-manager.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/wp-admin/theme-install.php b/src/wp-admin/theme-install.php index 65815ac7abd41..8e6fc5d1eea2c 100644 --- a/src/wp-admin/theme-install.php +++ b/src/wp-admin/theme-install.php @@ -68,7 +68,7 @@ 'themesFound' => __( 'Number of Themes found: %d' ), 'noThemesFound' => __( 'No themes found. Try a different search.' ), /* translators: %s: Theme name. */ - 'themeViewed' => __( 'Viewing theme: %s' ), + 'themeViewed' => __( 'Theme details: %s' ), 'collapseSidebar' => __( 'Collapse Sidebar' ), 'expandSidebar' => __( 'Expand Sidebar' ), /* translators: Hidden accessibility text. */ diff --git a/src/wp-admin/themes.php b/src/wp-admin/themes.php index 90ebf9f76edca..28ea47fda5903 100644 --- a/src/wp-admin/themes.php +++ b/src/wp-admin/themes.php @@ -237,7 +237,7 @@ 'themesFound' => __( 'Number of Themes found: %d' ), 'noThemesFound' => __( 'No themes found. Try a different search.' ), /* translators: %s: Theme name. */ - 'themeViewed' => __( 'Viewing theme: %s' ), + 'themeViewed' => __( 'Theme details: %s' ), ), ) ); diff --git a/src/wp-includes/class-wp-customize-manager.php b/src/wp-includes/class-wp-customize-manager.php index c2198acf20f66..e298b04efcf90 100644 --- a/src/wp-includes/class-wp-customize-manager.php +++ b/src/wp-includes/class-wp-customize-manager.php @@ -4959,7 +4959,7 @@ public function customize_pane_settings() { /* translators: %d: Number of themes being displayed, which cannot currently consider singular vs. plural forms. */ 'announceThemeCount' => __( 'Displaying %d themes' ), /* translators: %s: Theme name. */ - 'announceThemeDetails' => __( 'Showing details for theme: %s' ), + 'announceThemeDetails' => __( 'Theme details: %s' ), ), ); From 1fa386b6a01e559ff0ff9e455861566c9eb6c14c Mon Sep 17 00:00:00 2001 From: Joe Dolson Date: Sun, 26 Jul 2026 14:15:13 -0500 Subject: [PATCH 08/17] Add help text for theme viewer to communicate shortcuts --- src/wp-admin/themes.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/wp-admin/themes.php b/src/wp-admin/themes.php index 28ea47fda5903..2643fa2d051c0 100644 --- a/src/wp-admin/themes.php +++ b/src/wp-admin/themes.php @@ -133,7 +133,8 @@ '

' . __( 'From this screen you can:' ) . '

' . '
  • ' . __( 'Hover or tap to see Activate and Live Preview buttons' ) . '
  • ' . '
  • ' . __( 'Click on the theme to see the theme name, version, author, description, tags, and the Delete link' ) . '
  • ' . - '
  • ' . __( 'Click Customize for the active theme or Live Preview for any other theme to see a live preview' ) . '
' . + '
  • ' . __( 'Click Customize for the active theme or Live Preview for any other theme to see a live preview' ) . '
  • ' . + '
  • ' . __( 'Use the arrow buttons at the top of the dialog, or alt/option plus the left or right arrow keys on your keyboard, to navigate between theme details panels quickly.' ) . '
  • ' . '

    ' . __( 'The active theme is displayed highlighted as the first theme.' ) . '

    ' . '

    ' . __( 'The search for installed themes will search for terms in their name, description, author, or tag.' ) . ' ' . __( 'The search results will be updated as you type.' ) . '

    '; From 26f8003e37157c52dbb88b85d7172edb2efa920e Mon Sep 17 00:00:00 2001 From: Joe Dolson Date: Sun, 26 Jul 2026 14:27:00 -0500 Subject: [PATCH 09/17] debounce announcements --- src/js/_enqueues/wp/theme.js | 8 ++++---- src/js/media/views/frame/edit-attachments.js | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/js/_enqueues/wp/theme.js b/src/js/_enqueues/wp/theme.js index ee2ca21f77def..d228f46a4e891 100644 --- a/src/js/_enqueues/wp/theme.js +++ b/src/js/_enqueues/wp/theme.js @@ -558,7 +558,7 @@ themes.view.Theme = wp.Backbone.View.extend({ preview.render(); this.setNavButtonsState(); $( '.next-theme' ).trigger( 'focus' ); - themes.announceTheme( self.current ); + _.debounce( themes.announceTheme( self.current ), 500 ); }) .listenTo( preview, 'theme:previous', function() { @@ -589,7 +589,7 @@ themes.view.Theme = wp.Backbone.View.extend({ preview.render(); this.setNavButtonsState(); $( '.previous-theme' ).trigger( 'focus' ); - themes.announceTheme( self.current ); + _.debounce( themes.announceTheme( self.current ), 500 ); }); this.listenTo( preview, 'preview:close', function() { @@ -1337,7 +1337,7 @@ themes.view.Themes = wp.Backbone.View.extend({ // Trigger a route update for the current model. self.theme.trigger( 'theme:expand', nextModel.cid ); - themes.announceTheme( nextModel ); + _.debounce( themes.announceTheme( nextModel ), 500 ); } }, @@ -1365,7 +1365,7 @@ themes.view.Themes = wp.Backbone.View.extend({ // Trigger a route update for the current model. self.theme.trigger( 'theme:expand', previousModel.cid ); - themes.announceTheme( previousModel ); + _.debounce( themes.announceTheme( previousModel ), 500 ); } }, diff --git a/src/js/media/views/frame/edit-attachments.js b/src/js/media/views/frame/edit-attachments.js index bcaa8f562ab41..ff45129c55742 100644 --- a/src/js/media/views/frame/edit-attachments.js +++ b/src/js/media/views/frame/edit-attachments.js @@ -213,7 +213,7 @@ EditAttachments = MediaFrame.extend(/** @lends wp.media.view.MediaFrame.EditAtta this.trigger( 'refresh', model ); // Move focus to the Previous button. When there are no more items, to the Next button. this.focusNavButton( this.hasPrevious() ? '.left' : '.right' ); - this.announceMediaItem( model ); + _.debounce( this.announceMediaItem( model ), 500 ); }, /** @@ -230,7 +230,7 @@ EditAttachments = MediaFrame.extend(/** @lends wp.media.view.MediaFrame.EditAtta this.trigger( 'refresh', model ); // Move focus to the Next button. When there are no more items, to the Previous button. this.focusNavButton( this.hasNext() ? '.right' : '.left' ); - this.announceMediaItem( model ); + _.debounce( this.announceMediaItem( model ), 500 ); }, /** From e74c7f43e16b9c069eac4ed063a63b4c291621c7 Mon Sep 17 00:00:00 2001 From: Andrea Fercia Date: Mon, 27 Jul 2026 18:15:05 +0200 Subject: [PATCH 10/17] Fix debounce announcement implementation and add docblocks. --- src/js/_enqueues/wp/theme.js | 27 ++++++++++++---- src/js/media/views/frame/edit-attachments.js | 34 ++++++++++++++------ 2 files changed, 44 insertions(+), 17 deletions(-) diff --git a/src/js/_enqueues/wp/theme.js b/src/js/_enqueues/wp/theme.js index d228f46a4e891..4cb07ed057d92 100644 --- a/src/js/_enqueues/wp/theme.js +++ b/src/js/_enqueues/wp/theme.js @@ -16,7 +16,14 @@ themes = wp.themes = wp.themes || {}; themes.data = _wpThemeSettings; l10n = themes.data.l10n; -// Announce the theme shown after previous/next navigation. +/** + * Announces to screen readers the theme shown after previous/next navigation. + * + * @since 7.1.0 + * + * @param {Object} model The theme model. + * @return {void} + */ themes.announceTheme = function( model ) { var name = model.get( 'name' ) || model.get( 'id' ); @@ -409,6 +416,10 @@ themes.view.Theme = wp.Backbone.View.extend({ initialize: function() { this.model.on( 'change', this.render, this ); + + this.announceThemeDebounced = _.debounce( function( model ) { + themes.announceTheme( model ); + }, 500 ); }, render: function() { @@ -558,7 +569,7 @@ themes.view.Theme = wp.Backbone.View.extend({ preview.render(); this.setNavButtonsState(); $( '.next-theme' ).trigger( 'focus' ); - _.debounce( themes.announceTheme( self.current ), 500 ); + self.announceThemeDebounced( self.current ); }) .listenTo( preview, 'theme:previous', function() { @@ -589,7 +600,7 @@ themes.view.Theme = wp.Backbone.View.extend({ preview.render(); this.setNavButtonsState(); $( '.previous-theme' ).trigger( 'focus' ); - _.debounce( themes.announceTheme( self.current ), 500 ); + self.announceThemeDebounced( self.current ); }); this.listenTo( preview, 'preview:close', function() { @@ -1091,6 +1102,10 @@ themes.view.Themes = wp.Backbone.View.extend({ // Move the active theme to the beginning of the collection. self.currentTheme(); + this.announceThemeDebounced = _.debounce( function( model ) { + themes.announceTheme( model ); + }, 500 ); + // When the collection is updated by user input... this.listenTo( self.collection, 'themes:update', function() { self.parent.page = 0; @@ -1337,8 +1352,7 @@ themes.view.Themes = wp.Backbone.View.extend({ // Trigger a route update for the current model. self.theme.trigger( 'theme:expand', nextModel.cid ); - _.debounce( themes.announceTheme( nextModel ), 500 ); - + self.announceThemeDebounced( nextModel ); } }, @@ -1365,8 +1379,7 @@ themes.view.Themes = wp.Backbone.View.extend({ // Trigger a route update for the current model. self.theme.trigger( 'theme:expand', previousModel.cid ); - _.debounce( themes.announceTheme( previousModel ), 500 ); - + self.announceThemeDebounced( previousModel ); } }, diff --git a/src/js/media/views/frame/edit-attachments.js b/src/js/media/views/frame/edit-attachments.js index ff45129c55742..36ff9ba9b002b 100644 --- a/src/js/media/views/frame/edit-attachments.js +++ b/src/js/media/views/frame/edit-attachments.js @@ -34,7 +34,25 @@ EditAttachments = MediaFrame.extend(/** @lends wp.media.view.MediaFrame.EditAtta 'click .right': 'nextMediaItem' }, + /** + * Announces to screen readers the attachment shown after previous/next navigation. + * + * @since 7.1.0 + * + * @param {Object} model The attachment model. + * @return {void} + */ + announceMediaItem: function( model ) { + var title = model.get( 'title' ) || model.get( 'filename' ) || model.get( 'id' ); + + wp.a11y.speak( l10n.mediaItemViewed.replace( '%s', function() { + return title; + } ) ); + }, + initialize: function() { + var self = this; + Frame.prototype.initialize.apply( this, arguments ); _.defaults( this.options, { @@ -56,6 +74,10 @@ EditAttachments = MediaFrame.extend(/** @lends wp.media.view.MediaFrame.EditAtta this.title.mode( 'default' ); this.toggleNav(); + + this.announceMediaItemDebounced = _.debounce( function( model ) { + self.announceMediaItem( model ); + }, 500 ); }, bindHandlers: function() { @@ -213,7 +235,7 @@ EditAttachments = MediaFrame.extend(/** @lends wp.media.view.MediaFrame.EditAtta this.trigger( 'refresh', model ); // Move focus to the Previous button. When there are no more items, to the Next button. this.focusNavButton( this.hasPrevious() ? '.left' : '.right' ); - _.debounce( this.announceMediaItem( model ), 500 ); + this.announceMediaItemDebounced( model ); }, /** @@ -230,7 +252,7 @@ EditAttachments = MediaFrame.extend(/** @lends wp.media.view.MediaFrame.EditAtta this.trigger( 'refresh', model ); // Move focus to the Next button. When there are no more items, to the Previous button. this.focusNavButton( this.hasNext() ? '.right' : '.left' ); - _.debounce( this.announceMediaItem( model ), 500 ); + this.announceMediaItemDebounced( model ); }, /** @@ -244,14 +266,6 @@ EditAttachments = MediaFrame.extend(/** @lends wp.media.view.MediaFrame.EditAtta $( which ).trigger( 'focus' ); }, - announceMediaItem: function( model ) { - var title = model.get( 'title' ) || model.get( 'filename' ) || model.get( 'id' ); - - wp.a11y.speak( l10n.mediaItemViewed.replace( '%s', function() { - return title; - } ) ); - }, - getCurrentIndex: function() { return this.library.indexOf( this.model ); }, From 9092055dedc4a0e66de3f8ceb4a4693e5ce21bbe Mon Sep 17 00:00:00 2001 From: Andrea Fercia Date: Tue, 28 Jul 2026 09:23:51 +0200 Subject: [PATCH 11/17] Cancel pending media announcement when closing the dialog and clean up. --- src/js/media/views/frame/edit-attachments.js | 24 ++++++++++++-------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/src/js/media/views/frame/edit-attachments.js b/src/js/media/views/frame/edit-attachments.js index 36ff9ba9b002b..4347ebecc2901 100644 --- a/src/js/media/views/frame/edit-attachments.js +++ b/src/js/media/views/frame/edit-attachments.js @@ -42,17 +42,25 @@ EditAttachments = MediaFrame.extend(/** @lends wp.media.view.MediaFrame.EditAtta * @param {Object} model The attachment model. * @return {void} */ - announceMediaItem: function( model ) { - var title = model.get( 'title' ) || model.get( 'filename' ) || model.get( 'id' ); + announceMediaItemDebounced: _.debounce( function( model ) { + var title; + + if ( ! model ) { + return; + } + + title = model.get( 'title' ) || model.get( 'filename' ) || model.get( 'id' ); + + if ( ! title ) { + return; + } wp.a11y.speak( l10n.mediaItemViewed.replace( '%s', function() { return title; } ) ); - }, + }, 500 ), initialize: function() { - var self = this; - Frame.prototype.initialize.apply( this, arguments ); _.defaults( this.options, { @@ -74,10 +82,6 @@ EditAttachments = MediaFrame.extend(/** @lends wp.media.view.MediaFrame.EditAtta this.title.mode( 'default' ); this.toggleNav(); - - this.announceMediaItemDebounced = _.debounce( function( model ) { - self.announceMediaItem( model ); - }, 500 ); }, bindHandlers: function() { @@ -119,6 +123,8 @@ EditAttachments = MediaFrame.extend(/** @lends wp.media.view.MediaFrame.EditAtta // Move focus back to the original item in the grid if possible. $( 'li.attachment[data-id="' + this.model.get( 'id' ) +'"]' ).trigger( 'focus' ); this.resetRoute(); + // Cancel any pending navigation announcement. + this.announceMediaItemDebounced.cancel(); }, this ) ); // Set this frame as the modal's content. From cdfc1c69d39391f0685ad863b976cd50537f13c3 Mon Sep 17 00:00:00 2001 From: Andrea Fercia Date: Tue, 28 Jul 2026 11:12:04 +0200 Subject: [PATCH 12/17] Cancel pending theme announcement when closing the dialog and clean up. --- src/js/_enqueues/wp/theme.js | 38 ++++++++++++++++++++++-------------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/src/js/_enqueues/wp/theme.js b/src/js/_enqueues/wp/theme.js index 4cb07ed057d92..defced02ff403 100644 --- a/src/js/_enqueues/wp/theme.js +++ b/src/js/_enqueues/wp/theme.js @@ -24,13 +24,23 @@ l10n = themes.data.l10n; * @param {Object} model The theme model. * @return {void} */ -themes.announceTheme = function( model ) { - var name = model.get( 'name' ) || model.get( 'id' ); +themes.announceThemeDebounced = _.debounce( function( model ) { + var name; + + if ( ! model ) { + return; + } + + name = model.get( 'name' ) || model.get( 'id' ); + + if ( ! name ) { + return; + } wp.a11y.speak( l10n.themeViewed.replace( '%s', function() { return name; } ) ); -}; +}, 500 ); // Shortcut for isInstall check. themes.isInstall = !! themes.data.settings.isInstall; @@ -416,10 +426,6 @@ themes.view.Theme = wp.Backbone.View.extend({ initialize: function() { this.model.on( 'change', this.render, this ); - - this.announceThemeDebounced = _.debounce( function( model ) { - themes.announceTheme( model ); - }, 500 ); }, render: function() { @@ -569,7 +575,7 @@ themes.view.Theme = wp.Backbone.View.extend({ preview.render(); this.setNavButtonsState(); $( '.next-theme' ).trigger( 'focus' ); - self.announceThemeDebounced( self.current ); + themes.announceThemeDebounced( self.current ); }) .listenTo( preview, 'theme:previous', function() { @@ -600,7 +606,7 @@ themes.view.Theme = wp.Backbone.View.extend({ preview.render(); this.setNavButtonsState(); $( '.previous-theme' ).trigger( 'focus' ); - self.announceThemeDebounced( self.current ); + themes.announceThemeDebounced( self.current ); }); this.listenTo( preview, 'preview:close', function() { @@ -791,6 +797,9 @@ themes.view.Details = wp.Backbone.View.extend({ } }); } + + // Cancel any pending navigation announcement. + themes.announceThemeDebounced.cancel(); }, // Handles .disabled classes for next/previous buttons. @@ -989,6 +998,9 @@ themes.view.Preview = themes.view.Details.extend({ this.trigger( 'preview:close' ); this.undelegateEvents(); this.unbind(); + + // Cancel any pending navigation announcement. + themes.announceThemeDebounced.cancel(); return false; }, @@ -1102,10 +1114,6 @@ themes.view.Themes = wp.Backbone.View.extend({ // Move the active theme to the beginning of the collection. self.currentTheme(); - this.announceThemeDebounced = _.debounce( function( model ) { - themes.announceTheme( model ); - }, 500 ); - // When the collection is updated by user input... this.listenTo( self.collection, 'themes:update', function() { self.parent.page = 0; @@ -1352,7 +1360,7 @@ themes.view.Themes = wp.Backbone.View.extend({ // Trigger a route update for the current model. self.theme.trigger( 'theme:expand', nextModel.cid ); - self.announceThemeDebounced( nextModel ); + themes.announceThemeDebounced( nextModel ); } }, @@ -1379,7 +1387,7 @@ themes.view.Themes = wp.Backbone.View.extend({ // Trigger a route update for the current model. self.theme.trigger( 'theme:expand', previousModel.cid ); - self.announceThemeDebounced( previousModel ); + themes.announceThemeDebounced( previousModel ); } }, From 40041e5765720b7c0fd93a952230966c42d6ecf4 Mon Sep 17 00:00:00 2001 From: Andrea Fercia Date: Tue, 28 Jul 2026 11:16:07 +0200 Subject: [PATCH 13/17] Simplify replacement for wp.a11y.speak. --- src/js/_enqueues/wp/theme.js | 4 +--- src/js/media/views/frame/edit-attachments.js | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/js/_enqueues/wp/theme.js b/src/js/_enqueues/wp/theme.js index defced02ff403..a9964de581372 100644 --- a/src/js/_enqueues/wp/theme.js +++ b/src/js/_enqueues/wp/theme.js @@ -37,9 +37,7 @@ themes.announceThemeDebounced = _.debounce( function( model ) { return; } - wp.a11y.speak( l10n.themeViewed.replace( '%s', function() { - return name; - } ) ); + wp.a11y.speak( l10n.themeViewed.replace( '%s', name ) ); }, 500 ); // Shortcut for isInstall check. diff --git a/src/js/media/views/frame/edit-attachments.js b/src/js/media/views/frame/edit-attachments.js index 4347ebecc2901..f6bb4b8afa2cc 100644 --- a/src/js/media/views/frame/edit-attachments.js +++ b/src/js/media/views/frame/edit-attachments.js @@ -55,9 +55,7 @@ EditAttachments = MediaFrame.extend(/** @lends wp.media.view.MediaFrame.EditAtta return; } - wp.a11y.speak( l10n.mediaItemViewed.replace( '%s', function() { - return title; - } ) ); + wp.a11y.speak( l10n.mediaItemViewed.replace( '%s', title ) ); }, 500 ), initialize: function() { From f345b8ee386837e49b4b0638c0ce8efcc18f3019 Mon Sep 17 00:00:00 2001 From: Andrea Fercia Date: Tue, 28 Jul 2026 11:47:29 +0200 Subject: [PATCH 14/17] Debounce the announcement in the Customizer Themes Section. --- src/js/_enqueues/wp/customize/controls.js | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/js/_enqueues/wp/customize/controls.js b/src/js/_enqueues/wp/customize/controls.js index afcd3986ad3a5..915553c44073d 100644 --- a/src/js/_enqueues/wp/customize/controls.js +++ b/src/js/_enqueues/wp/customize/controls.js @@ -1701,6 +1701,7 @@ filtersHeight: 0, headerContainer: null, updateCountDebounced: null, + announceThemeDebounced: null, /** * wp.customize.ThemesSection @@ -1724,6 +1725,13 @@ section.$body = $( document.body ); api.Section.prototype.initialize.call( section, id, options ); section.updateCountDebounced = _.debounce( section.updateCount, 500 ); + section.announceThemeDebounced = _.debounce( function( name ) { + if ( ! name ) { + return; + } + + wp.a11y.speak( api.settings.l10n.announceThemeDetails.replace( '%s', name ) ); + }, 500 ); }, /** @@ -2607,7 +2615,8 @@ section.$body.addClass( 'modal-open' ); section.containFocus( section.overlay ); section.updateLimits(); - wp.a11y.speak( api.settings.l10n.announceThemeDetails.replace( '%s', theme.name ) ); + + section.announceThemeDebounced( theme.name ); if ( callback ) { callback(); } @@ -2625,6 +2634,8 @@ section.$body.removeClass( 'modal-open' ); section.overlay.fadeOut( 'fast' ); api.control( section.params.action + '_theme_' + section.currentTheme ).container.find( '.theme' ).focus(); + // Cancel any pending navigation announcement. + section.announceThemeDebounced.cancel(); }, /** From 930aaab6abb4abc8189c9d737367f354bfaeb42c Mon Sep 17 00:00:00 2001 From: Andrea Fercia Date: Tue, 28 Jul 2026 12:04:16 +0200 Subject: [PATCH 15/17] Prevent default on Customizer arrow keys navigation. --- src/js/_enqueues/wp/customize/controls.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/js/_enqueues/wp/customize/controls.js b/src/js/_enqueues/wp/customize/controls.js index 915553c44073d..00123f9141c30 100644 --- a/src/js/_enqueues/wp/customize/controls.js +++ b/src/js/_enqueues/wp/customize/controls.js @@ -1792,11 +1792,13 @@ // Pressing the right arrow key fires a theme:next event. if ( 39 === event.keyCode ) { + event.preventDefault(); // Prevent browser from triggering history shortcuts. section.nextTheme(); } // Pressing the left arrow key fires a theme:previous event. if ( 37 === event.keyCode ) { + event.preventDefault(); // Prevent browser from triggering history shortcuts. section.previousTheme(); } From b4a70c500c82c9ae198dc1708bf0bd48bbdce52e Mon Sep 17 00:00:00 2001 From: Andrea Fercia Date: Tue, 28 Jul 2026 12:33:08 +0200 Subject: [PATCH 16/17] Update QUnit tests. --- tests/qunit/wp-admin/js/theme.js | 59 ++++++++++++++++++++++++++++---- 1 file changed, 53 insertions(+), 6 deletions(-) diff --git a/tests/qunit/wp-admin/js/theme.js b/tests/qunit/wp-admin/js/theme.js index c17a5d59d41f9..d82b26e1521db 100644 --- a/tests/qunit/wp-admin/js/theme.js +++ b/tests/qunit/wp-admin/js/theme.js @@ -12,16 +12,18 @@ nextTheme: function() { nextCalled++; }, previousTheme: function() { prevCalled++; }, keyEvent: function( event ) { - if ( event.shiftKey || event.ctrlKey || event.altKey || event.metaKey ) { + if ( event.shiftKey || event.ctrlKey || event.metaKey ) { return; } // Right arrow - if ( event.keyCode === 39 ) { + if ( event.altKey && event.keyCode === 39 ) { + event.preventDefault(); this.nextTheme(); } // Left arrow - else if ( event.keyCode === 37 ) { + else if ( event.altKey && event.keyCode === 37 ) { + event.preventDefault(); this.previousTheme(); } } @@ -34,28 +36,70 @@ themePreview = createThemePreview(); }); - QUnit.test( 'Arrow keys without modifiers', function( assert ) { + QUnit.test( 'Arrow keys with Alt modifier', function( assert ) { // Right arrow themePreview.keyEvent( $.Event( 'keydown', { keyCode: 39, + altKey: true, shiftKey: false, ctrlKey: false }) ); - assert.equal( nextCalled, 1, 'Right arrow triggers nextTheme' ); + assert.equal( nextCalled, 1, 'Alt + Right arrow triggers nextTheme' ); // Left arrow themePreview.keyEvent( $.Event( 'keydown', { keyCode: 37, + altKey: true, shiftKey: false, ctrlKey: false }) ); - assert.equal( prevCalled, 1, 'Left arrow triggers previousTheme' ); + assert.equal( prevCalled, 1, 'Alt + Left arrow triggers previousTheme' ); } ); + QUnit.test( 'Arrow keys without Alt do nothing', function( assert ) { + // Right arrow without Alt - should NOT call nextTheme + themePreview.keyEvent( $.Event( 'keydown', { + keyCode: 39, + altKey: false, + shiftKey: false, + ctrlKey: false + }) ); + assert.equal( nextCalled, 0, 'Right arrow without Alt does nothing' ); + + // Left arrow without Alt - should NOT call previousTheme + themePreview.keyEvent( $.Event( 'keydown', { + keyCode: 37, + altKey: false, + shiftKey: false, + ctrlKey: false + }) ); + assert.equal( prevCalled, 0, 'Left arrow without Alt does nothing' ); + } ); + + QUnit.test( 'PreventDefault is called for arrow keys with Alt', function( assert ) { + // This test would need to check if preventDefault was called + var event = $.Event( 'keydown', { + keyCode: 39, + altKey: true, + shiftKey: false, + ctrlKey: false + }); + + // Mock the preventDefault method to track if it's called + var preventDefaultCalled = false; + event.preventDefault = function() { + preventDefaultCalled = true; + }; + + themePreview.keyEvent( event ); + assert.ok( preventDefaultCalled, 'preventDefault is called for arrow keys with Alt' ); + }); + QUnit.test( 'Shift+Arrow keys do nothing', function( assert ) { // Shift + Right themePreview.keyEvent( $.Event( 'keydown', { keyCode: 39, + altKey: false, shiftKey: true, ctrlKey: false }) ); @@ -64,6 +108,7 @@ // Shift + Left themePreview.keyEvent( $.Event( 'keydown', { keyCode: 37, + altKey: false, shiftKey: true, ctrlKey: false }) ); @@ -74,6 +119,7 @@ // Ctrl + Right themePreview.keyEvent( $.Event( 'keydown', { keyCode: 39, + altKey: false, ctrlKey: true, shiftKey: false }) ); @@ -82,6 +128,7 @@ // Ctrl + Left themePreview.keyEvent( $.Event( 'keydown', { keyCode: 37, + altKey: false, ctrlKey: true, shiftKey: false }) ); From 7fb34d0ea3c7f2e304d043c6c2309ef5c2b0c4cd Mon Sep 17 00:00:00 2001 From: Andrea Fercia Date: Tue, 28 Jul 2026 20:24:06 +0200 Subject: [PATCH 17/17] Improve help text and wrap key names within code tags. --- src/wp-admin/themes.php | 8 ++++---- src/wp-admin/upload.php | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/wp-admin/themes.php b/src/wp-admin/themes.php index 2643fa2d051c0..ac2cd4a9824cb 100644 --- a/src/wp-admin/themes.php +++ b/src/wp-admin/themes.php @@ -131,10 +131,10 @@ if ( current_user_can( 'switch_themes' ) ) { $help_overview = '

    ' . __( 'This screen is used for managing your installed themes. Aside from the default theme(s) included with your WordPress installation, themes are designed and developed by third parties.' ) . '

    ' . '

    ' . __( 'From this screen you can:' ) . '

    ' . - '
    • ' . __( 'Hover or tap to see Activate and Live Preview buttons' ) . '
    • ' . - '
    • ' . __( 'Click on the theme to see the theme name, version, author, description, tags, and the Delete link' ) . '
    • ' . - '
    • ' . __( 'Click Customize for the active theme or Live Preview for any other theme to see a live preview' ) . '
    • ' . - '
    • ' . __( 'Use the arrow buttons at the top of the dialog, or alt/option plus the left or right arrow keys on your keyboard, to navigate between theme details panels quickly.' ) . '
    ' . + '
    • ' . __( 'Hover or tap to see Activate and Live Preview buttons.' ) . '
    • ' . + '
    • ' . __( 'Click Customize for the active theme or Live Preview for any other theme to see a live preview.' ) . '
    • ' . + '
    • ' . __( 'Click on a theme to open the Theme Details dialog and see the theme name, version, author, description, tags, and the Delete link.' ) . '
    • ' . + '
    • ' . __( 'Use the buttons at the top of the dialog, or alt/option plus the left or right arrow keys on your keyboard, to navigate between themes quickly.' ) . '
    ' . '

    ' . __( 'The active theme is displayed highlighted as the first theme.' ) . '

    ' . '

    ' . __( 'The search for installed themes will search for terms in their name, description, author, or tag.' ) . ' ' . __( 'The search results will be updated as you type.' ) . '

    '; diff --git a/src/wp-admin/upload.php b/src/wp-admin/upload.php index 5ceee4c2208da..7cf0f6fe10108 100644 --- a/src/wp-admin/upload.php +++ b/src/wp-admin/upload.php @@ -190,7 +190,7 @@ function () { 'title' => __( 'Attachment Details' ), 'content' => '

    ' . __( 'Clicking an item will display an Attachment Details dialog, which allows you to preview media and make quick edits. Any changes you make to the attachment details will be automatically saved.' ) . '

    ' . - '

    ' . __( 'Use the arrow buttons at the top of the dialog, or alt/option plus the left or right arrow keys on your keyboard, to navigate between media items quickly.' ) . '

    ' . + '

    ' . __( 'Use the buttons at the top of the dialog, or alt/option plus the left or right arrow keys on your keyboard, to navigate between media items quickly.' ) . '

    ' . '

    ' . __( 'You can also delete individual items and access the extended edit screen from the details dialog.' ) . '

    ', ) );