I have:
Bug description
A brand font family whose name contains a bare number does not apply in HTML output. The text falls back to the browser default serif font.
Quarto writes the family name into the generated CSS without quotes. For Source Sans 3 the output is font-family:Source Sans 3. A bare 3 is not a valid CSS identifier, so the browser discards the whole declaration.
The @import for the font is correct and the font downloads. This makes the problem look like a font-loading error, but the font is never used.
Quotes in the YAML do not help. Quarto strips them before it generates the CSS.
The same family name works when it is set with document metadata instead:
mainfont: Source Sans 3 # produces font-family:"Source Sans 3" — works
Related but different: #11947 reports Source Sans 3 as a _brand.yml font, and that thread diagnosed a Typst font-name mismatch. The HTML side was noted as unexplained there. This issue is the HTML cause.
Steps to reproduce
Render this self-contained document to HTML:
---
title: Font test
format: html
brand:
typography:
fonts:
- family: Source Sans 3
source: google
base: Source Sans 3
---
Hello world.
Open the result in a browser, or read the generated index_files/libs/bootstrap/bootstrap-*.min.css.
A _brand/_brand.yml file with the same typography block produces byte-identical CSS.
Actual behavior
The generated CSS holds the family name without quotes:
@import"https://fonts.googleapis.com/css2?family=Source+Sans+3:ital,wght@0,400;0,700;1,400;1,700&display=swap";
...
:root,[data-bs-theme=light]{...--bs-body-font-family: Source Sans 3;...}
...
font-family:Source Sans 3
The computed font-family on body in Chromium is Times.
Scope, measured on Quarto 1.11.4:
| Element |
Affected |
Note |
base |
yes |
$font-family-base, $mainFont, $mermaid-font-family |
headings |
yes |
$headings-font-family, $presentation-heading-font |
monospace, monospace-inline, monospace-block |
yes |
falls back to generic monospace |
link |
no |
link has no family property in the schema |
| Format |
Affected |
html |
yes |
revealjs |
yes (--r-main-font, --r-heading-font, --r-code-font) |
typst |
no — the Typst emitter writes font: ("Source Sans 3",), always quoted |
| Font source |
Affected |
google |
yes |
bunny |
yes |
file |
yes |
The file source shows the inconsistency inside one stylesheet. The @font-face rule quotes the family, the variable that uses it does not:
@font-face{font-family:"My Font 3";src:url("fonts/myfont.woff2");font-weight:400;font-style:normal}
...
font-family:My Font 3
The trigger is any family name with a space-separated part that starts with a digit. Source Sans 3, Bricolage Grotesque 96pt, M PLUS 1 Code and M PLUS Rounded 1c all fail. Roboto Mono works, because both parts are valid identifiers.
Cause
brandTypographyLayer interpolates the value straight into the Sass variable, with no quoting for family names:
|
let value = fontInformation[source]; |
|
if (["color", "background-color"].includes(source)) { |
|
value = brand.getColor(value as string); |
|
} |
|
typographyVariables.push( |
|
`$${target}: ${value} !default;`, |
|
); |
A helper for this already exists. asCssFont quotes any font name that contains a space:
|
export function asCssFont(value: unknown): string | undefined { |
|
if (!value) { |
|
return undefined; |
|
} else { |
|
const fontFamily = String(value) |
|
.split(",") |
|
.map((font) => { |
|
font = font.trim(); |
|
if ( |
|
font.includes(" ") && !font.startsWith('"') && !font.startsWith("'") |
|
) { |
|
font = `"${font}"`; |
|
} |
|
return font; |
|
}) |
|
.filter((font) => font.length > 0) |
|
.join(", "); |
|
return `${fontFamily}`; |
|
} |
|
} |
It is used for the mainfont and monofont metadata path, which is why mainfont: Source Sans 3 works:
|
add(explicitVars, "font-family-base", metadata["mainfont"], asCssFont); |
|
add(explicitVars, "font-family-code", metadata["monofont"], asCssFont); |
The brand path does not use it. Applying asCssFont when source is family would match the existing metadata path, the @font-face rule that fileFontImportString already emits with quotes, and Bootstrap's own defaults in the same stylesheet (--bs-font-sans-serif: system-ui, -apple-system, "Segoe UI", Roboto, …).
Expected behavior
The generated CSS quotes the family name, and the font applies:
font-family:"Source Sans 3"
With the quotes added by hand to the generated stylesheet, the computed font-family on body becomes "Source Sans 3" and the font renders.
Your environment
- IDE: Positron 1.130.0
- OS: macOS 26.6.2 (build 25G83)
AI assistance was used to investigate this, grounded in a local clone of the repository, as described in Using AI tools to investigate.
Quarto check output
Quarto 1.11.4
[✓] 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.11.4
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...
I have:
Bug description
A brand font family whose name contains a bare number does not apply in HTML output. The text falls back to the browser default serif font.
Quarto writes the family name into the generated CSS without quotes. For
Source Sans 3the output isfont-family:Source Sans 3. A bare3is not a valid CSS identifier, so the browser discards the whole declaration.The
@importfor the font is correct and the font downloads. This makes the problem look like a font-loading error, but the font is never used.Quotes in the YAML do not help. Quarto strips them before it generates the CSS.
The same family name works when it is set with document metadata instead:
Related but different: #11947 reports
Source Sans 3as a_brand.ymlfont, and that thread diagnosed a Typst font-name mismatch. The HTML side was noted as unexplained there. This issue is the HTML cause.Steps to reproduce
Render this self-contained document to HTML:
Open the result in a browser, or read the generated
index_files/libs/bootstrap/bootstrap-*.min.css.A
_brand/_brand.ymlfile with the sametypographyblock produces byte-identical CSS.Actual behavior
The generated CSS holds the family name without quotes:
The computed
font-familyonbodyin Chromium isTimes.Scope, measured on Quarto 1.11.4:
base$font-family-base,$mainFont,$mermaid-font-familyheadings$headings-font-family,$presentation-heading-fontmonospace,monospace-inline,monospace-blockmonospacelinklinkhas nofamilyproperty in the schemahtmlrevealjs--r-main-font,--r-heading-font,--r-code-font)typstfont: ("Source Sans 3",), always quotedgooglebunnyfileThe
filesource shows the inconsistency inside one stylesheet. The@font-facerule quotes the family, the variable that uses it does not:The trigger is any family name with a space-separated part that starts with a digit.
Source Sans 3,Bricolage Grotesque 96pt,M PLUS 1 CodeandM PLUS Rounded 1call fail.Roboto Monoworks, because both parts are valid identifiers.Cause
brandTypographyLayerinterpolates the value straight into the Sass variable, with no quoting for family names:quarto-cli/src/core/sass/brand.ts
Lines 543 to 549 in abc6a78
A helper for this already exists.
asCssFontquotes any font name that contains a space:quarto-cli/src/core/css.ts
Lines 85 to 104 in abc6a78
It is used for the
mainfontandmonofontmetadata path, which is whymainfont: Source Sans 3works:quarto-cli/src/format/html/format-html-scss.ts
Lines 481 to 482 in abc6a78
The brand path does not use it. Applying
asCssFontwhensourceisfamilywould match the existing metadata path, the@font-facerule thatfileFontImportStringalready emits with quotes, and Bootstrap's own defaults in the same stylesheet (--bs-font-sans-serif: system-ui, -apple-system, "Segoe UI", Roboto, …).Expected behavior
The generated CSS quotes the family name, and the font applies:
With the quotes added by hand to the generated stylesheet, the computed
font-familyonbodybecomes"Source Sans 3"and the font renders.Your environment
AI assistance was used to investigate this, grounded in a local clone of the repository, as described in Using AI tools to investigate.
Quarto check output