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 )
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()doesattachment = 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 thequery-attachmentsresponse 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 samewp.media.attachment( id )+fetch()pattern inonOpenfor 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)
- Use a site with normal (non-localhost) latency, so the
get-attachmentAJAX response arrives after the media modal has rendered. - Create a post and set as featured image an image that has a title.
- 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).
- 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)
Change History (7)
@
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
@
9 hours ago
Replying to joedolson:
This is largely a duplicate of #65438, but includes an additional suggestion about the
renderpath 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)".
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)

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