Skip to content

a11y: all about templates render the title block, image, and links outside <main> #14605

@cwickham

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

On a website page that uses an about: template, the about chrome — the title
block (#title-block-header), the profile image, and the links — is rendered
outside the page's <main> landmark. This produces three axe-core
violations on every built-in about template (jolla, trestles, marquee,
solana, broadside):

  • landmark-no-duplicate-banner#title-block-header is a <header> that is
    no longer nested in <main>, so it implicitly becomes a second banner
    landmark alongside the navbar's #quarto-header.
  • landmark-unique — the two banners have no distinguishing accessible name.
  • region — the image and links sit outside every landmark.

An otherwise identical page with no about: has none of these: there the title
block renders inside <main> (so it is not a banner), and there is no stray
image/links.

This is related to #14375 / #14376 / #14377, but a distinct code path: those
arise from a listing page with title-block-banner: true, whereas this comes
from the about post-processor re-emitting the title block as a sibling of
<main>. The element targets (.about-image / .about-links) are not the ones
listed in #14377.

I traced the cause to the about post-processor and the EJS templates. It lifts
#title-block-header out of the DOM and captures <main class="content"> as a
string, then each template emits the title (a <header>), the image, and the
links as siblings of <main> inside a plain <div class="quarto-about-*">:

  • post-processor:
    const aboutPagePostProcessor = (
    aboutPage: AboutPage,
    pipeline: MarkdownPipeline,
    ) => {
    return (
    doc: Document,
    ): Promise<HtmlPostProcessResult> => {
    // Grab the title and remove it.
    const titleEl = doc.getElementById("title-block-header");
    titleEl?.remove();
    const title = titleEl?.outerHTML || "";
    // Grab the about element
    let aboutEl = aboutPage.id ? doc.getElementById(aboutPage.id) : undefined;
    if (!aboutEl) {
    aboutEl = doc.querySelector("main.content");
    }
    const body = aboutEl?.outerHTML || "";
    const ejsData: AboutPageEjsData = {
    title,
    body,
    image: aboutPage.image,
    [kImageAlt]: aboutPage[kImageAlt],
    [kImageTitle]: aboutPage[kImageTitle],
    links: aboutPage.links,
    options: aboutPage.options,
    };
    // Render the template
    const aboutPageHtml = renderEjs(
    aboutPage.template,
    { about: ejsData },
    true,
    !aboutPage.custom && !quartoConfig.isDebug(),
    );
    // Replace the about page contents with the updated contents
    const aboutPageContainer = doc.createElement("div");
    aboutPageContainer.innerHTML = aboutPageHtml;
    aboutEl?.after(...aboutPageContainer.childNodes);
    aboutEl?.remove();
    const result: HtmlPostProcessResult = {
    resources: [],
    supporting: [],
    };
    if (aboutPage.image) {
    result.resources.push(aboutPage.image);
    }
    // Update any rendered items
    pipeline.processRenderedMarkdown(doc);
    return Promise.resolve(result);
    };
    };
  • templates: https://github.com/quarto-dev/quarto-cli/tree/e768e5c2d34381173aa3d43d8d3a332125647561/src/resources/projects/website/about

Investigation was AI-assisted and grounded in a local clone of quarto-cli
(per CONTRIBUTING.md, "Using AI tools to investigate").

Steps to reproduce

Full reproducible website (one page per template + a control):
https://github.com/cwickham/quarto-about-template-a11yquarto preview
and open any page to see the axe report overlay.

Minimal version — a two-file website project (the navbar supplies the first
banner, so a website project is needed to surface landmark-no-duplicate-banner):

_quarto.yml

project:
  type: website
website:
  navbar:
    right:
      - text: Home
        href: index.qmd
format:
  html:
    axe:
      output: document

index.qmd

---
title: "Alicia"
subtitle: "Data Scientist"
image: profile.jpg
image-alt: "A descriptive alt text"
about:
  template: jolla
  links:
    - icon: github
      text: Github
      href: https://github.com
---

A little bit about me.

Then quarto preview and open the page. Swapping template: to trestles,
marquee, solana, or broadside reproduces the same three violations.

Actual behavior

All five about templates emit the title block, image, and links outside
<main>, producing three violations each:

Template landmark-no-duplicate-banner landmark-unique region
(no about)
jolla img, .about-links
trestles img, .about-links
marquee .about-image-container, .about-footer
solana .about-links, img
broadside .about-links (image is a CSS background)

The page body itself stays inside <main> on every template — only the title
block, image, and links are stranded outside it.

Expected behavior

The about page's title block, image, and links are page-specific primary
content, not site chrome. They should be contained within the page's single
<main> landmark
, and the navbar should be the only banner — matching
the structure of a normal (non-about) Quarto page, where #title-block-header
already renders inside <main>. With that, none of the three violations fire.

Your environment

  • IDE: n/a — reproduced with the Quarto CLI directly from the terminal (editor: Positron)
  • OS: macOS 26.5.1 (build 25F80)

Quarto check output

Quarto 1.10.8
[✓] 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.8
      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
      knitr: 1.51
      rmarkdown: 2.31
[✓] Checking Knitr engine render......OK
[✓] Checking Python 3 installation....OK
      Version: 3.12.2
      Jupyter: 5.9.1
      Kernels: python3
[✓] Checking Jupyter engine render....OK
[✓] Checking Julia installation.......OK

Metadata

Metadata

Assignees

No one assigned

    Labels

    accessibilitybugSomething isn't workinghtmlIssues with HTML and related web technology (html/css/scss/js)

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions