Make WordPress Core

Opened 9 hours ago

Last modified 8 hours ago

#65852 new defect (bug)

Media grid tile label (aria-label) is stale: shows "uploading…" / "(no title)" for titled attachments whose models load after the view is created

Reported by: archon810 Owned by:
Priority: normal Milestone: Awaiting Review
Component: Media Version: 7.0.3
Severity: normal Keywords: needs-testing has-test-info has-screenshots
Cc: Focuses: ui, accessibility, javascript

Description (last modified by archon810)

It's been a while since I reported a core WP bug, but I have to because it's a pretty annoying one. Of course, Cursor and Fable 5 assisted me in root-causing, but I assure you I'm a real human (Artem Russakovskii) reporting a real bug.

Since WordPress 7.0, the Media Library grid shows each attachment tile's aria-label as a visible label overlay (.wp-core-ui .attachment:not(:has(.filename))::after { content: attr(aria-label); }, added in [62104] for #64883).

However, wp.media.view.Attachment computes that attribute only once, when the view's element is created - Backbone only evaluates attributes() during _ensureElement():

attributes: function() {
	...
	return {
		'tabIndex':     0,
		'role':         'checkbox',
		'aria-label':   ariaLabel,
		'aria-checked': false,
		'data-id':      this.model.get( 'id' )
	};
},

When a tile view is created for an attachment model that has not finished loading yet, the label is computed from an empty model and falls back to "uploading…" (or "(no title)" once [62892] / #65438 ships in 7.1). When the model's data arrives, the view re-renders - but render() only replaces the element's inner HTML, so the stale aria-label (and therefore the visible label and the accessible name) is never corrected. The tile stays labeled "uploading…" forever, even though the attachment has a title.

This is not an edge case. Core itself creates id-only models with a fetch in flight:

  • wp.media.controller.FeaturedImage.updateSelection() does attachment = Attachment.get( id ); attachment.fetch(); and puts that model into the selection, which is synced into the library. The tile for the current featured image is therefore created before the fetch resolves whenever the AJAX response takes longer than the modal render - i.e. on any real site with normal latency. (Even when the query-attachments response later includes the same attachment, its data merges into the existing model instance, which only re-renders the tile's inner HTML - the element attribute stays stale.)
  • Gutenberg's MediaUpload (@wordpress/media-utils) uses the same wp.media.attachment( id ) + fetch() pattern in onOpen for current values (featured image, galleries), so the block editor is affected the same way.
  • Any plugin following the documented wp.media.attachment( id ).fetch() pattern to preload selections hits this too.

Symptom from a user's perspective: open a post that already has a featured image, click the featured image to replace it - the currently-featured tile in the grid is labeled "uploading…" even though nothing is uploading and the image has a title (the Attachment Details sidebar shows the correct title). Switching to another screen and back (e.g. "Upload files" → "Media Library", or Edit Gallery → Add to Gallery → back) recreates the views and the labels fix themselves, confirming it's a stale attribute rather than missing data.

Note this is distinct from #65438 (milestoned for 7.1): that fix ([62892]) only changes which fallback string attributes() picks for a titleless model. It does not address the staleness. Verified: applying the exact [62892] change to the 7.0.2/7.0.3 reproduction environment below (patched built media-views.js / media-views.min.js, cache-busted) changes the stuck label from "uploading…" to "(no title)" - a permanently wrong label on an attachment that has a title (see attached screenshot. The stale value never updates to the real title in either case.

Screen reader impact: the stale accessible name predates 7.0 (the attribute was always computed once), but 7.0 made it visible to everyone.

Steps to reproduce (stock WordPress)

  1. Use a site with normal (non-localhost) latency, so the get-attachment AJAX response arrives after the media modal has rendered.
  2. Create a post and set as featured image an image that has a title.
  3. Reload the post edit screen and click the featured image thumbnail to open the "Featured image" modal (classic editor metabox, or Replace in the block editor).
  4. Look at the selected tile in the Media Library grid.
  • Expected: the tile label shows the attachment title.
  • Actual: the tile label shows "uploading…" ("(no title)" after [62892]) and never updates. Switching to "Upload files" and back to "Media Library" fixes it.

One-click reproduction in WordPress Playground

Open the one-click reproduction in WordPress Playground (self-contained link; the entire setup below is encoded in the URL).

The blueprint creates a titled image ("My titled image", dated 2020), 5 newer "Filler image N" attachments (for contrast: their labels render correctly next to the broken one), and a post "Featured image label repro" with the 2020 image as its featured image. Because everything in Playground is local and near-instant, the race that occurs naturally on real sites is made deterministic by an mu-plugin that adds a 2-second delay to the get-attachment admin-ajax response (the request issued by wp.media.model.Attachment.fetch()); a second hook makes the media modal default to the Media Library tab so no extra clicks are needed.

To reproduce: open the Playground link, go to Posts → "Featured image label repro" → click the featured image thumbnail in the Featured image metabox. The selected tile shows "uploading…" and keeps it indefinitely, while every other tile is labeled correctly and the Attachment Details sidebar shows the real title. Switch "Upload files" → "Media Library" to watch it self-heal.

Verified on WordPress 7.0.2/7.0.3 / PHP 8.3 in Playground (labels sampled at 0.7 s, 2.7 s, and 5.7 s after opening the modal - the stale label persists while wp.media.attachment( id ).get( 'title' ) returns the correct title). Re-verified with the #65438 fix ([62892]) applied on top: same staleness, label stuck at "(no title)" instead.

Suggested fix

Re-apply the accessible name during render() so it catches up once the model has data, e.g. in src/js/media/views/attachment.js factor the label computation out of attributes():

getAriaLabel: function() {
	var ariaLabel = this.model.get( 'title' );

	if ( ! ariaLabel ) {
		if ( this.model.get( 'uploading' ) ) {
			ariaLabel = wp.i18n.__( 'uploading…' );
		} else {
			ariaLabel = wp.i18n.__( '(no title)' );
		}
	}

	return ariaLabel;
},

and in render() after the template is applied:

// `attributes()` only runs when the view element is created, which can be
// before the model has been fetched. Keep the accessible name in sync.
if ( this.$el.attr( 'aria-label' ) !== undefined ) {
	this.$el.attr( 'aria-label', this.getAriaLabel() );
}

The undefined guard skips subclasses that deliberately reset the inherited attributes (wp.media.view.Attachment.Details, see #47458), and leaves aria-checked alone so selection state set by updateSelect() is not clobbered. Views re-render on every model change (rerenderOnModelChange defaults to true), so no new listeners are needed; genuine uploads keep showing "uploading…" until the upload finishes and the server response populates the model.

Attachments (2)

Screenshot 2026-08-11 142441.png (119.5 KB ) - added by archon810 9 hours ago.
Screenshot 2026-08-11 142644.png (86.1 KB ) - added by archon810 9 hours ago.
An actual screenshot from our customized WordPress instance where the gallery shows all of the items as "uploading..."

Download all attachments as: .zip

Change History (7)

#1 follow-up: @joedolson
9 hours ago

This is largely a duplicate of #65438, but includes an additional suggestion about the render path that's worth looking into.

@archon810
9 hours ago

An actual screenshot from our customized WordPress instance where the gallery shows all of the items as "uploading..."

#2 in reply to: ↑ 1 @archon810
9 hours ago

Replying to joedolson:

This is largely a duplicate of #65438, but includes an additional suggestion about the render path that's worth looking into.

Indeed, I reference #65438 several times in the report, but I had Fable test the patch slated for 7.1 from there (also as mentioned in the report), and the bug is still present, albeit instead of "uploading...", it shows "(no title)".

Last edited 9 hours ago by archon810 (previous) (diff)

#3 @archon810
8 hours ago

  • Description modified (diff)
  • Keywords has-test-info has-screenshots added; has-patch removed
  • Version7.0.3

#4 @joedolson
8 hours ago

Yes, that was a comment to ensure that this doesn't get closed as a duplicate.

#5 @archon810
8 hours ago

Here is the same Playground blueprint but on current trunk showing (no title) instead of uploading....

https://i.imgur.com/nxSyZdr.png

Note: See TracTickets for help on using tickets.