From ca01d2f0eceec52c8e7aa21a0af06d3cdffea1ef Mon Sep 17 00:00:00 2001 From: Khokan Sardar Date: Wed, 2 Sep 2026 20:48:34 +0530 Subject: [PATCH] Media: Fix ARIA labelling and list semantics in the media views. The media modal labelled itself with a fixed `media-frame-title` ID that was set on the frame's heading container. In the Media Library grid that ID is claimed by the manage frame's empty heading, so the Edit attachment dialog had no accessible name, and any page that opened more than one media frame ended up with duplicate IDs. Generate the ID on the modal instance, set it directly on the frame's H1 heading, and reference it from the dialog element. Also set `role="group"` on the attachments list. Its `li` children carry `role="checkbox"`, and ARIA in HTML only allows that if the role of the list itself is changed as well. Fixes #66025. --- src/js/media/views/attachments.js | 1 + src/js/media/views/media-frame.js | 4 +++- src/js/media/views/modal.js | 7 ++++++- src/wp-includes/media-template.php | 4 ++-- 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/js/media/views/attachments.js b/src/js/media/views/attachments.js index 5b8d91426c38c..a4712eab01497 100644 --- a/src/js/media/views/attachments.js +++ b/src/js/media/views/attachments.js @@ -8,6 +8,7 @@ Attachments = View.extend(/** @lends wp.media.view.Attachments.prototype */{ className: 'attachments', attributes: { + role: 'group', tabIndex: -1 }, diff --git a/src/js/media/views/media-frame.js b/src/js/media/views/media-frame.js index 99dfc675729da..4cc645ff908fd 100644 --- a/src/js/media/views/media-frame.js +++ b/src/js/media/views/media-frame.js @@ -164,9 +164,11 @@ MediaFrame = Frame.extend(/** @lends wp.media.view.MediaFrame.prototype */{ * @this wp.media.controller.Region */ createTitle: function( title ) { + // A modal's dialog element points `aria-labelledby` at its frame heading. title.view = new wp.media.View({ controller: this, - tagName: 'h1' + tagName: 'h1', + attributes: this.modal ? { id: this.modal.titleId } : {} }); }, /** diff --git a/src/js/media/views/modal.js b/src/js/media/views/modal.js index 42f8da3c5024a..629329ed88789 100644 --- a/src/js/media/views/modal.js +++ b/src/js/media/views/modal.js @@ -32,6 +32,8 @@ Modal = wp.media.View.extend(/** @lends wp.media.view.Modal.prototype */{ hasCloseButton: true }); + this.titleId = _.uniqueId( 'media-frame-title-' ); + this.focusManager = new wp.media.view.FocusManager({ el: this.el }); @@ -39,12 +41,15 @@ Modal = wp.media.View.extend(/** @lends wp.media.view.Modal.prototype */{ /** * Prepares the data for the modal template. * + * @since 7.2.0 Added the `titleId` property. + * * @return {Object} The prepared data. */ prepare: function() { return { title: this.options.title, - hasCloseButton: this.options.hasCloseButton + hasCloseButton: this.options.hasCloseButton, + titleId: this.titleId }; }, diff --git a/src/wp-includes/media-template.php b/src/wp-includes/media-template.php index 460cf3b3020e5..70e75e0d28567 100644 --- a/src/wp-includes/media-template.php +++ b/src/wp-includes/media-template.php @@ -178,7 +178,7 @@ function wp_print_media_templates() {