Repository navigation
a11y: default and grid listings emit empty <a> when an item description contains a link (link-name) #15211
Description
When a listing item description contains a link, default and grid listings render empty, focusable <a> elements. axe-core reports them as link-name, keyboard users tab onto invisible links, and part of the item is no longer a link to the item.
This is a regression from 1.7. With this website (_quarto.yml with project: type: website):
---
title: "Listing"
listing:
- id: default-listing
type: default
contents:
- path: https://example.com/article
title: "Article"
description: "Read the article, see the [docs](https://example.org/docs)."
- id: grid-listing
type: grid
contents:
- path: https://example.com/article
title: "Article"
description: "Read the article, see the [docs](https://example.org/docs)."
---
::: {#default-listing}
:::
::: {#grid-listing}
:::Quarto 1.6.43 renders the description link as text, inside a single link to the item:
<div class="listing-description">
<a href="https://example.com/article" class="no-external">Read the article, see the <span>docs</span>.</a>
</div>Quarto 1.10.19 keeps the description link, nested in the item link. The browser splits the nested anchors, so the item link is left empty, and in the grid listing the card link is closed before the description:
<div class="delink listing-description"><a href="https://example.com/article" class="no-external">
</a><p><a href="https://example.com/article" class="no-external">Read the article, see the </a><a href="https://example.org/docs">docs</a>.</p></a><div class="card-text listing-description delink"><a href="https://example.com/article" class="quarto-grid-link">
</a><p><a href="https://example.com/article" class="quarto-grid-link">Read the article, see the </a><a href="https://example.org/docs">docs</a>.</p>The delink filter is there for this. It turns links inside a Div with the delink class into spans, so the whole description stays one link to the item:
quarto-cli/src/resources/filters/quarto-post/delink.lua
Lines 6 to 30 in 4bebcb4
| function delink() | |
| return { | |
| -- Removes links from any divs marked with 'delink' class | |
| Div = function(div) | |
| if _quarto.format.isHtmlOutput() and div.attr.classes:includes(kDelinkClass) then | |
| -- remove the delink class | |
| for i, clz in ipairs(div.attr.classes) do | |
| if clz == kDelinkClass then | |
| div.attr.classes:remove(i) | |
| end | |
| end | |
| -- find links and transform them to spans | |
| -- this is in post, so it's after render, so we don't need to worry about | |
| -- custom ast nodes | |
| return _quarto.traverser(div, { | |
| Link = function(link) | |
| return pandoc.Span(link.content) | |
| end | |
| }) | |
| end | |
| end | |
| } | |
| end |
It only matches Pandoc Divs. Up to 1.6 the templates wrote <div class="delink listing-description"> as HTML in the Markdown, which Pandoc parses into a Div. f9a5250 and 43ff451 (for #11758) moved it into a raw HTML block, so there is no Div anymore and the filter does nothing. The delink class is even left in the output, as the filter removes it when it runs.
quarto-cli/src/resources/projects/website/listing/item-default.ejs.md
Lines 89 to 101 in 4bebcb4
| <% if (fields.includes('description')) { %> | |
| ```{=html} | |
| <div class="delink listing-description"><a href="<%- item.path %>" class="no-external-icon"> | |
| ``` | |
| <%= item.description %> | |
| ```{=html} | |
| </a></div> | |
| ``` | |
| <% } %> |
quarto-cli/src/resources/projects/website/listing/item-grid.ejs.md
Lines 98 to 110 in 4bebcb4
| <% if (showField('description')) { %> | |
| ```{=html} | |
| <div class="card-text listing-description delink"> | |
| ``` | |
| <%= item.description %> | |
| ```{=html} | |
| </div> | |
| ``` | |
| <% } %> |
We could write this container as a fenced div again (::: {.delink .listing-description}), keeping only the item link as raw HTML, like the ::: {.body} div the template already uses. This gives the 1.6 output back.
Description links not being clickable is intended, see #10814 where making them clickable is discussed as an option. The table listing is not affected, as its description is not inside a link.
Related: #14380, #15129, #15066 (other empty links in default listings), #9469 (its listing axe test excludes these empty links for now).
Activity
Fix empty item links in listings when a description contains a link (#…