From db79f13cc7c907833c9112d3f243849926165a01 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 b2e91624cb159..0d90c720d9844 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 1140bf2795ff4..afdacf5a2102e 100644 --- a/src/js/media/views/media-frame.js +++ b/src/js/media/views/media-frame.js @@ -158,9 +158,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 29150515a45cd..c8da850c1246f 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() {