quarto-dev / quarto-dev/quarto-cli
typst book: TOC entries, links, and heading numbers unreadable when brand primary is a bright color
Nobody has claimed this yet.
- Dominant language
- JavaScript
- Stars
- 6k
- Forks
- 458
- Avg merge
- 1d 9h
- Merged PRs (30d)
- 41
Description
Description
In a Typst book project with a _brand.yml that sets a bright primary color (and a separate foreground for body text), the bundled orange-book extension applies brand.primary as the text fill for:
- TOC entries (chapter and section names, plus chapter/section numbers)
- All links
- Heading numbers for level 2+ headings (e.g.
1.1) - Citations
These elements become unreadable when primary is a bright color, even though foreground is set to a readable text color and is correctly applied to body text and chapter heading titles.
Body text and chapter heading titles (level 1) are correct — they pick up brand.foreground via the #set text(fill: brand-color.foreground) rule and the #show heading: set text(fill: foreground) rule emitted by typst-brand-yaml.lua. The unreadable elements bypass that wiring and read main-color directly.
Reproduction
_quarto.yml:
project:
type: book
brand: _brand.yml
book:
title: "Reproduction"
chapters:
- index.qmd
- chapter1.qmd
format:
typst: default
_brand.yml — bright yellow primary, dark foreground on cream background:
color:
palette:
sw-yellow: "#FFE81F"
sw-black: "#0B0B0F"
sw-cream: "#F5F0E1"
primary: sw-yellow
foreground: sw-black
background: sw-cream
index.qmd:
# Preface {.unnumbered}
This contains a [link to Quarto](https://quarto.org).
chapter1.qmd:
# First chapter
A paragraph with a [link](https://quarto.org).
## A sub-section
Body text.
Render with quarto render. The cover renders correctly (pale yellow band with dark title), but on subsequent pages every TOC entry and every link renders in yellow on the cream page background, and the 1.1 heading number renders in yellow.
Three layers involved
To make the discussion concrete, three layers of code participate:
-
The Lua filter
typst-brand-yaml.luareads_brand.ymland emits the Typst header that declares#let brand-color = (...),#set text(fill: brand-color.foreground), and the#show linkrule. -
Quarto's orange-book wrapper (
typst-show.typundersrc/resources/extension-subtrees/orange-book/_extensions/orange-book/) is Quarto-controlled Typst code that consumesbrand-colorand calls into the upstream package. This is the natural place for any Quarto-specific brand wiring. -
The upstream Typst package
@preview/orange-book:0.7.1is vendored atsrc/resources/extension-subtrees/orange-book/_extensions/orange-book/typst/packages/preview/orange-book/0.7.1/. The bundled copy can be modified, but doing so diverges from the published package ontypst.app/universe; any later update of the vendored copy would have to re-apply local patches (or have them upstreamed).
Where main-color is read in the upstream package
Layer 2 — Quarto's wrapper — wires a single main-color parameter from brand.primary:
Inside layer 3 — the bundled book() function — main-color is used for both decorative roles (cover tint, heading box strokes, theorem strokes, exercise box borders, lighten() backgrounds) and text-fill roles. There is no separate parameter for text fill:
Text-fill uses of main-color in the upstream package:
-
TOC entries:
textColor: main-color
https://github.com/quarto-dev/quarto-cli/blob/4ed3ffb6016c9c049be1caca588fb784889c97f1/src/resources/extension-subtrees/orange-book/_extensions/orange-book/typst/packages/preview/orange-book/0.7.1/my-outline.typ#L49 -
Heading number text for level 2–3:
set text(fill: main-color) if it.level < 4
https://github.com/quarto-dev/quarto-cli/blob/4ed3ffb6016c9c049be1caca588fb784889c97f1/src/resources/extension-subtrees/orange-book/_extensions/orange-book/typst/packages/preview/orange-book/0.7.1/lib.typ#L537 -
Link text:
show link: set text(fill: main-color)
https://github.com/quarto-dev/quarto-cli/blob/4ed3ffb6016c9c049be1caca588fb784889c97f1/src/resources/extension-subtrees/orange-book/_extensions/orange-book/typst/packages/preview/orange-book/0.7.1/lib.typ#L638 -
Citation text:
show cite: it => { show regex("[\w\W]"): set text(main-color); it }
https://github.com/quarto-dev/quarto-cli/blob/4ed3ffb6016c9c049be1caca588fb784889c97f1/src/resources/extension-subtrees/orange-book/_extensions/orange-book/typst/packages/preview/orange-book/0.7.1/lib.typ#L405
The upstream book() already has a cover-background: auto parameter that decouples the cover tint from main-color, but typst-show.typ does not set it and no equivalent parameter exists for text fills.
Why this is hard to work around
Users cannot redefine brand-color before the orange-book template reads it. The Quarto-generated header places user include-in-header content before the brand-color let declaration emitted by typst-brand-yaml.lua, so a user document has no entry point to override main-color.
The only known workaround is to ship a local override extension that replaces the bundled typst-show.typ (using the local extension override added in #14332) and passes a different main-color. That requires the user to know about the bundled extension's existence.
Where the fix can live
Two options, in order of how much they touch layer 3 (the vendored upstream package):
Option 1 — Layer 2 only (Quarto wrapper). Change typst-show.typ to pass main-color: brand-color.at("foreground", default: black) and explicitly pass cover-background: brand-color.at("primary", default: blue).lighten(70%) to preserve the cover tint. Pure layer-2 change, no divergence from upstream. Trade-off: every other use of main-color (heading box strokes, theorem strokes, exercise borders, line() stroke under heading-style 2) becomes the foreground color — the brand accent is lost for those decorative elements.
Option 2 — Layer 2 plus layer 3. Add a text-color parameter to book() (defaulting to main-color for backward compatibility) and route the text-fill uses (TOC entries, heading numbers, link text, citation text) through it. Pass text-color: brand-color.at("foreground", default: black) from typst-show.typ. Result: text becomes readable, decorative uses keep the brand accent.
Option 2 means modifying book() in lib.typ (and the corresponding signatures in my-outline.typ). That change has to land somewhere — and there are two non-exclusive ways to ship it:
- Patch the vendored copy of the package at
src/resources/extension-subtrees/orange-book/_extensions/orange-book/typst/packages/preview/orange-book/0.7.1/. Fix ships in the next Quarto release; the local copy diverges from upstream until the vendored version is bumped to one that incorporates the change. - Contribute the parameter upstream at https://github.com/flavio20002/typst-orange-template and wait for a published version that includes it. No local divergence, but the fix waits on upstream's release cadence (last push 2026-01-30, so the repo is active but not on a fast cadence).
The two are compatible: patch the vendored copy now to unblock users, contribute upstream in parallel, drop the local patch when the vendored version is bumped.
Option 1 is purely a Quarto-side change and may be acceptable as an interim mitigation, but Option 2 produces the cleanest visual outcome.
Related
- #14092 — callout colors with brand.yml, same family of issue (decorative vs readable text use of brand colors).
- #14270 — exposing more orange-book variables to YAML (separate concern, but same surface area).
- #13950 — original orange-book bundling PR.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with src/resources/extension-subtrees/orange-book/_extensions/orange-book/typst-show.typ, then trace the bundled package in typst/packages/preview/orange-book/0.7.1/lib.typ and my-outline.typ. Compare the two proposed ways to separate readable text color from decorative brand color, and render the provided reproduction to verify that TOC entries, links, citations, and heading numbers are readable while the cover and decorative accents retain their intended color.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- lua, yaml
- Domain
- documentation
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100