Skip to content

a11y: page-layout: custom renders the title block header outside <main> #14839

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 page that uses page-layout: custom, Quarto's own title block (#title-block-header) is rendered outside the page's <main> landmark, so it implicitly becomes a second banner landmark alongside the navbar's #quarto-header.

#title-block-header is a <header>, and a <header> computes as banner unless it is inside <main> or sectioning content (HTML-AAM §3.5.49). With the default layout the title block renders inside main#quarto-document-content, so it is not a banner and the page has one. With page-layout: custom there is no <main> at all, so the title block is never nested.

This is the same problem as #14375, #14377 and #14605#title-block-header ending up outside <main> — but a different code path. In #14375 the title-block post-processor moves the header out of #quarto-content when title-block-banner: true or title-block-style: manuscript is set. In #14605 the about post-processor re-emits it as a sibling of <main>. Here nothing moves it: the custom layout never opens a <main> for it to sit in.

before-body-article.ejs opens <main class="content">, while before-body-custom.ejs opens only a div, and formatHasArticleLayout selects between them:

Authors cannot work around this. page-layout: custom hands the body container to the author, so an author can add their own <main> and does get a main landmark. It does not help: Quarto emits #title-block-header before the author's content, so the title block stays outside whatever the author wraps, and the page still has two banners.

Steps to reproduce

A website with page-layout: custom.

_quarto.yml:

project:
  type: website
website:
  title: "Custom layout site"
  navbar:
    left:
      - href: index.qmd
        text: Home
format:
  html:
    page-layout: custom

index.qmd:

---
title: Custom layout in a website
---

Body text.

Render the site, open _site/index.html, and count the landmarks in the browser console:

document.querySelectorAll('header').length   // 2, both computing as banner
document.querySelectorAll('main').length     // 0

Removing page-layout: custom from _quarto.yml gives one banner and one main.

Actual behavior

Two banner landmarks and no main. Measured in Chromium with the same site rendered both ways:

default layout        banners: 1   mains: 1
  header#quarto-header        parent=body                          insideMain=false
  header#title-block-header   parent=main#quarto-document-content  insideMain=true

page-layout: custom   banners: 2   mains: 0
  header#quarto-header        parent=body                          insideMain=false
  header#title-block-header   parent=div#quarto-content            insideMain=false

axe reports two best-practice violations, both moderate:

  • landmark-no-duplicate-banner — "Document should not have more than one banner landmark"
  • landmark-one-main — "Document should have one main landmark"

Adding an author-supplied <main> around the body content clears landmark-one-main (mains: 1) but not the duplicate banner (banners: 2).

A standalone document with page-layout: custom has one banner rather than two, since there is no navbar, but the body content still sits outside every landmark (region).

Expected behavior

The title block that Quarto emits does not become a second banner landmark.

Your environment

  • IDE: Positron 2026.08.0 (build 272)
  • OS: macOS 26.5.2 (build 25F84)

Quarto check output

Quarto 1.10.18
[✓] Checking environment information...
      Quarto cache location: /Users/charlottewickham/Library/Caches/quarto
[✓] Checking versions of quarto binary dependencies...
      Pandoc version 3.10.0: OK
      Dart Sass version 1.101.0: OK
      Deno version 2.7.14: OK
      Typst version 0.15.1: OK
[✓] Checking versions of quarto dependencies......OK
[✓] Checking Quarto installation......OK
      Version: 1.10.18
      Path: /Applications/quarto/bin
[✓] Checking tools....................OK
      TinyTeX: v2026.04
      Chrome Headless Shell: 150.0.7871.115
      VeraPDF: 1.28.2
[✓] Checking LaTeX....................OK
      Using: TinyTex
      Path: /Users/charlottewickham/Library/TinyTeX/bin/universal-darwin
      Version: 2026
[✓] Checking Chrome Headless....................OK
      Using: Chrome Headless Shell installed by Quarto
      Path: /Users/charlottewickham/Library/Application Support/quarto/chrome-headless-shell/chrome-headless-shell-mac-arm64/chrome-headless-shell
      Version: 150.0.7871.115
[✓] Checking basic markdown render....OK
[✓] Checking R installation...........OK
      Version: 4.5.2
      Path: /Library/Frameworks/R.framework/Versions/4.5-arm64/Resources
      LibPaths:
        - /Users/charlottewickham/Library/R/arm64/4.5/library
        - /Library/Frameworks/R.framework/Versions/4.5-arm64/Resources/library
      knitr: 1.51
      rmarkdown: 2.30
[✓] 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...

Found while reviewing #14739. Part of #8706. AI-assisted investigation, grounded in a local clone of quarto-cli.

Metadata

Metadata

Assignees

No one assigned

    Labels

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

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions