Repository navigation
Conversation
The default listing template wraps every item thumbnail in a link to the item. A post with no preview image, in a listing with no image-placeholder, gets an empty placeholder div in that link, so the page has one link per such post with no accessible name that is still a Tab stop. axe-core reports it as link-name (WCAG 2.4.4, 4.1.2). The template cannot tell at render time whether an image will be found, since the placeholder is only resolved once the post is rendered. So when the placeholder is replaced by the empty div and sits directly inside a link, that link now gets aria-hidden="true" and tabindex="-1". The markup is otherwise unchanged, so the layout stays the same, and links that do show an image are not touched. The title link next to the thumbnail goes to the same page. - Resolve the empty placeholder through replaceWithEmptyDiv() in website-listing-read.ts, which hides a link that wraps only the placeholder; the other attributes of the link are kept - Add a smoke test rendering a default listing with a post whose image comes from the front matter, one whose image is found in the rendered body, and one with no image, with and without image-placeholder Fixes quarto-dev#15066
Collaborator
✅ Snyk checks have passed. No issues have been found so far.
💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse. |
Both this branch and main (quarto-dev#15080) added an entry at the same place in news/changelog-1.11.md. Kept both, in issue-number order. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QCs32K1rV7d8ZZc9jRAAcV
Contributor
Author
|
Merged main to resolve a conflict in news/changelog-1.11.md: main's #15080 entry landed in the same spot as this one, so I kept both, in issue-number order. No other changes. |
cderv
approved these changes
Oct 20, 2026
cderv
left a comment
Member
There was a problem hiding this comment.
Thanks for the PR. The new test fails on main with the assertion you quote and passes with the change, and the other listing tests still pass.
About your questions:
- Hiding is fine. The empty thumbnail stays a mouse target like the ones showing an image, and the markup does not change for themes and custom templates.
- Fine as is. With a
tablelisting usingfield-links: [image, title], the empty image link is hidden and the title link stays reachable. The only case where it matters isfield-links: [image]without any other linked field, where the hidden link was the only link to the post. That one was already not usable before (link without a name), so I don't think it is worth special casing here. - Yes, separately. #15038 and #7028 already track it.
There is a new conflict in news/changelog-1.11.md with main. No need to push anything, we'll merge main on the branch before merging.
…mpty-thumbnail-link # Conflicts: # news/changelog-1.11.md
3 of 5 tasks
cderv
added a commit
that referenced
this pull request
Oct 20, 2026
The default listing template wrapped the metadata block and the subtitle line in links to the post even when neither showed any visible text. Those links had no accessible name, which axe-core flags as a link-name violation and which made an empty Tab stop for keyboard users. Each link is now written only when its content renders visible text. - Write the metadata link only when one of its fields has visible text. The .metadata div stays on every item so body widths match within a listing. - Write the subtitle line only when the post has a subtitle. - Treat falsy values, empty lists and blank strings as no visible text. The empty thumbnail link had the same problem and was fixed separately in #15099. Fixes #14380 Fixes #15129
4 of 5 tasks
cderv
pushed a commit
that referenced
this pull request
Oct 27, 2026
…#9469) With `link-external-newwindow: true`, external items in default and grid listings still opened in the same tab. They carried `no-external`, which makes the external-link script skip them entirely, turning off both the new tab and the icon. A new `no-external-icon` class turns off only the icon, so listing items now open in a new tab without gaining an icon. - Default and grid listing items use `no-external-icon`; `no-external` keeps its meaning, and links inside item descriptions keep their icon. - The external-link decoration reruns when pagination, filtering or sorting shows new items, and adds a string `link-external-icon` only once per link. - Links opened in a new tab are announced as "(opens in a new tab)" (WCAG technique G201) through `aria-describedby`, from the new language key `link-external-newwindow-label`, so their accessible name is unchanged. A link title stays a tooltip but is no longer exposed as the accessible description. - The empty thumbnail link of external items without an image is now hidden, extending #15099 to items without a file. - `no-external-icon` is stripped from llms output. Fixes #9468
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
In a default listing, a post with no preview image gets an empty thumbnail link.
item-default.ejs.mdwraps the thumbnail of every item in<a href="…" class="no-external">, and when the post has no image and the listing sets noimage-placeholder,emptyDiv()inwebsite-listing-read.tsfills that link with<div class="listing-item-img-placeholder"> </div>. The link has no accessible name but is still a Tab stop, so keyboard and screen reader users meet one unnamed link per post before its title link. axe-core reports it aslink-name.The template only knows about an image the post declares in its front matter (
image). Otherwise it emits a placeholder comment, andcompleteListingItems()resolves it once the post has been rendered: a preview image found in the rendered page (.preview-image, a feature/cover/thumbnail image, or the first local image), the listing'simage-placeholder, or the empty div. So the fix sits at that last step. When the placeholder becomes the empty div and sits directly inside a link, the link getsaria-hidden="true"andtabindex="-1". Its other attributes are kept and the markup is otherwise unchanged, so the layout stays the same and links that do show an image are not touched. The title link next to the thumbnail goes to the same page.Before:
After:
I hid the link rather than removing it because that keeps the DOM identical for CSS and custom templates, and it is one of the two outcomes the issue asks for. A custom template that puts the placeholder inside a link next to other content is left alone, since that link has content of its own.
The new smoke test renders a default listing with three posts (image in the front matter, image found in the rendered body, no image), once without
image-placeholder(index.qmd) and once with it (placeholder.qmd). Onmainit fails withRequired DOM Element div.thumbnail a[aria-hidden="true"][tabindex="-1"] div.listing-item-img-placeholder is missing; with this change it passes.Run on Windows 11 with the dev build (
configure.cmd):The
smoke-alllisting documents underlistings/image-lazy-loading/blog/,2026/10/12/issue-15022,2024/10/23/issue-10829,2024/12/03/issue-11580,2025/04/01/issue-8238andwebsite/llms-txt-listingpass as well (11 passed).2024/10/28/issue-10311fails on this machine with and without the change because its posts need a Python kernel that is not set up here.axe-core 4.14 on the rendered
index.html: thelink-namefinding on.thumbnail > .no-external[href="./posts/no-image/index.html"]is gone, and noaria-hidden-focusfinding appears. Thelink-nameandimage-altfindings that remain on thumbnails that do show an image come from those<img>having noalt, which is #15038 / #7028 and not changed here.The axe-core check ran in headless Chromium (Playwright's
chromium_headless_shell), not with a screen reader.deno fmt --checkonwebsite-listing-read.tsflags lines 18–21 (an import block); that complaint exists onmainunchanged and I left it alone. The added code is fmt-clean anddeno lintpasses.Open questions
<a>would be a small change in the same helper if you prefer no link at all.tablelisting withfield-links: [image]gets the same treatment. The default table listing uses a<span>, so default output is unchanged. Fine, or should it be limited to the default template?alt(a11y:quarto create project blogscaffold has images with no alt text and icon-only navbar links with no name #15038, Add support foralt(and maybetitle) forplaceholder-imagein listings #7028). Left untouched on purpose; happy to follow up separately.Fixes #15066
Checklist
I have (if applicable):
AI-assisted PR
Note: autonomous AI agents submitting PRs without human oversight are not permitted — see the Code of Conduct.