Skip to content

a11y: logos without alt text are handled inconsistently across formats #14660

Description

@cwickham

I have:

  • searched the issue tracker for similar issues
  • installed the latest version of Quarto CLI
  • formatted my issue following the Bug Reports guide

Bug description

Logo alt text is settable across formats — websites via logo-alt, typst and revealjs via logo: {path, alt}.
This issue is about the default when no alt is provided: the format pipelines disagree, and two of them produce inaccessible output rather than a safe fallback.

When a logo (navbar/sidebar logo, revealjs slide logo, or typst logo) is rendered without alt text, they emit:

Path Emitted markup for a logo with no alt Source
HTML website navbar <img … alt="" …> navbrand.ejs#L6-L9
HTML website sidebar <img … alt="" …> sidebar.ejs#L40-L43
revealjs slide logo <img …>alt attribute omitted format-reveal.ts#L409-L416
Typst title-page logo image(...)alt parameter omitted page.typ#L14

HTML defaults a missing logo alt to an empty alt=""; revealjs and Typst leave the attribute off entirely.

This matters because empty vs. absent alt are not equivalent for accessibility:

  • <img alt=""> is a valid decorative-image signal — assistive tech skips it, and axe-core's image-alt rule passes.
  • <img> with no alt attribute fails axe-core image-alt (tagged wcag2a/wcag111 → WCAG 2.2 SC 1.1.1 Non-text Content, Level A), and screen readers commonly announce the image filename instead.

So on the same input (a logo with no alt), a Quarto website is conformant while a reveal deck produces a Level A failure — and the Typst PDF's logo has no text alternative.

Investigation was AI-assisted, grounded in a local clone of this repo (source pinned to 50f3d27) and reproduced with Quarto 1.10.12, per CONTRIBUTING.md's note on using AI tools.

The design question

Fixing the inconsistency forces a decision about the intended default, which is really the point of this issue.
Our options are:

  1. Assume logos are decorative → emit alt="" everywhere. Make revealjs/Typst match HTML. Simplest; no broken output; passes axe. Downside: it silently passes — an author who forgot alt on a genuinely meaningful logo is never told, because alt="" conforms.
  2. Emit alt="" everywhere and warn at render time when a logo resolves with no alt, plus an explicit opt-out to mark a logo decorative and suppress the warning. Alerts every author at build time while keeping output safe. Downside: more work; needs a "decorative" flag and could be noisy for logos that legitimately sit next to visible title text.
  3. Something else?

I think omit alt as a default isn't an option. This leads to an outright 1.1.1 failure that will only get picked up if an author runs a check.

So at minimum revealjs and typst should stop omitting the attribute. The open question is whether the default should stay a silent alt="" (option 1) or gain a warning (option 2).

For context, logos are often decorative but not always. For example, a navbar logo that includes the site/organization name (that isn't otherwise in the navbar).

Steps to reproduce

---
title: "Logo alt test"
format: revealjs
logo: logo.svg
---

## Slide one

Content.

Render, then inspect the emitted logo:

quarto render deck.qmd
grep -oE '<img[^>]*slide-logo[^>]*>' deck.html

Actual behavior

Same input, no alt specified — divergent output:

revealjs:  <img src="logo.svg" class="slide-logo">                       # no alt
website:   <img src="./logo.svg" alt="" class="navbar-logo light-content">  # alt=""

The revealjs <img> has no alt attribute → axe-core image-alt violation (WCAG 1.1.1, Level A); screen readers may read logo.svg. The Typst pipeline likewise omits the alt: parameter on the title-page logo.

Expected behavior

Consistent handling across formats.

Your environment

  • IDE: Positron (CLI render; IDE-independent)
  • OS: macOS 26.5.1 (25F80)

Quarto check output

Quarto 1.10.12
[✓] Checking environment information...
      Quarto cache location: /Users/charlottewickham/Library/Caches/quarto
[✓] Checking versions of quarto binary dependencies...
      Pandoc version 3.8.3: OK
      Dart Sass version 1.87.0: OK
      Deno version 2.7.14: OK
      Typst version 0.14.2: OK
[✓] Checking versions of quarto dependencies......OK
[✓] Checking Quarto installation......OK
      Version: 1.10.12
      Path: /Applications/quarto/bin

[✓] Checking tools....................OK
      TinyTeX: v2026.04
      VeraPDF: 1.28.2
      Chrome Headless Shell: (not installed)

[✓] Checking LaTeX....................OK
      Using: TinyTex
      Path: /Users/charlottewickham/Library/TinyTeX/bin/universal-darwin
      Version: 2026

[✓] Checking Chrome Headless....................OK
      Using: Chrome found on system
      Path: /Applications/Google Chrome.app/Contents/MacOS/Google Chrome
      Source: MacOS known location

[✓] Checking basic markdown render....OK

[✓] Checking R installation...........OK
      Version: 4.6.0
      Path: /Library/Frameworks/R.framework/Versions/4.6/Resources
      LibPaths:
        - /Users/charlottewickham/Library/R/arm64/4.6/library
        - /Library/Frameworks/R.framework/Versions/4.6/Resources/library
      knitr: 1.51
      rmarkdown: 2.31

[✓] Checking Knitr engine render......OK

[✓] Checking Python 3 installation....OK
      Version: 3.12.2
      Path: /Users/charlottewickham/.pyenv/versions/3.12.2/bin/python3
      Jupyter: 5.9.1
      Kernels: python3

[✓] Checking Jupyter engine render....OK

[✓] Checking Julia installation...

Metadata

Metadata

Assignees

Labels

Type

No type

Fields

No fields configured for issues without a type.

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions