Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
quarto-dev
GitHub Repository: quarto-dev/quarto-cli
Path: blob/main/llm-docs/revealjs-format-architecture.md
6832 views
---
main_commit: 98be8a667 analyzed_date: 2026-02-23 key_files: - src/format/reveal/format-reveal.ts - src/format/reveal/constants.ts - src/resources/formats/revealjs/pandoc/template.html - src/resources/formats/revealjs/pandoc/revealjs.template
---

Revealjs Format Architecture

How Quarto configures reveal.js presentations, covering metadata handling, template rendering, and the division of responsibilities between TypeScript and Pandoc templates.

Key Files

FileRole
src/format/reveal/format-reveal.tsFormat definition, metadata normalization, extras
src/format/reveal/constants.tsMetadata key constants
src/resources/formats/revealjs/pandoc/template.htmlQuarto's active Pandoc template
src/resources/formats/revealjs/pandoc/revealjs.templateReference copy of Pandoc's upstream template

Two Template Files

Quarto maintains two revealjs template files. They serve different purposes:

revealjs.template is a direct copy of Pandoc's default.revealjs, auto-generated by package/src/common/update-pandoc.ts during Pandoc version updates. Do not modify this file — changes will be overwritten on the next Pandoc update.

template.html is Quarto's active template, specified in formatExtras() via templateContext. It diverges from the upstream template where Quarto needs different behavior (type-safe rendering, additional features, etc.).

Metadata Handling Pattern

Revealjs configuration flows through three stages with distinct responsibilities:

Stage 1: Normalization — revealResolveFormat()

Maps user-facing YAML keys to reveal.js configuration keys. Runs early in format resolution.

Responsibilities:

  • Map compound YAML structures to flat metadata (e.g., scroll-view.snapscrollSnap)

  • Normalize values (e.g., navigationMode: "vertical""default")

  • Create helper flags for template type handling (e.g., scrollProgressAuto)

  • Remove intermediate metadata keys (e.g., delete scroll-view after extracting sub-options)

Does NOT set defaults — only transforms what the user provided.

Stage 2: Defaults — extras.metadata in formatExtras()

Sets opinionated default values that can be overridden by user metadata. These are the lowest priority in the metadata chain.

// General revealjs defaults extras.metadata = { ...extras.metadata, ...revealMetadataFilter({ width: 1050, height: 700, center: false, transition: "none", // ... }), }; // Conditional defaults (e.g., scroll-view options when view is "scroll") if (format.metadata[kView] === "scroll") { extras.metadata = { ...extras.metadata, [kScrollSnap]: "mandatory", [kScrollLayout]: "full", [kScrollActivationWidth]: 0, }; }

Setting defaults explicitly (rather than relying on Pandoc's defField) ensures the template always has values to render.

Stage 3: Post-processing — fixupRevealJsInitialization()

DOM manipulation of the rendered HTML, handling values that can't be fixed in the template:

  • Quoting slideNumber string values (e.g., h.v'h.v')

  • Quoting percentage-based width/height values

  • Injecting extraConfig values (options not in the template)

  • Registering plugins

Use this stage only when template-level handling isn't possible.

Metadata Priority (highest to lowest)

  1. metadataOverride — forces values regardless of user settings

  2. format.metadata — user values + normalization from revealResolveFormat()

  3. Pandoc defField — Pandoc writer defaults for unset variables

  4. extras.metadata — Quarto's opinionated defaults

Template Type Handling

Pandoc templates render values as text. This creates type mismatches when reveal.js expects specific JavaScript types. Quarto's template.html uses conditional guards to render correct JS types.

The Problem

Pandoc template variables have limited type awareness:

  • $var$ renders BoolVal True as true, BoolVal False as false (correct for JS booleans)

  • '$var$' always renders as a quoted string, even for false'false' (wrong — truthy in JS)

  • Numbers rendered inside quotes become strings: '$var$' with 0'0' (wrong if JS expects a number)

Pattern: Mixed-Type Options

When an option accepts both strings and booleans (e.g., scrollSnap: "mandatory" | "proximity" | false):

$if(scrollSnap)$ scrollSnap: '$scrollSnap/nowrap$', $else$ scrollSnap: false, $endif$

This works because Pandoc's $if()$ evaluates BoolVal False as false, so:

  • String values ("mandatory", "proximity") → $if$ is true → quoted output

  • Boolean false$if$ is false → $else$ renders unquoted false

Pattern: String "auto" with Boolean Fallback

When an option accepts "auto" | true | false (e.g., scrollProgress), a helper flag avoids rendering "auto" as a boolean:

TypeScript (normalization stage):

if (value === "auto" || value === undefined) { format.metadata[kHelperFlag] = true; delete format.metadata[kOriginalKey]; }

Template:

$if(helperFlag)$ option: 'auto', $elseif(option)$ option: $option$, $else$ option: false, $endif$

Pattern: Numeric Options

When reveal.js checks typeof value === 'number', the template must NOT quote the value:

scrollActivationWidth: $scrollActivationWidth$,

Not '$scrollActivationWidth$' — that renders as string '0' instead of number 0.

Known Pandoc Template Issues

Pandoc's upstream revealjs.template (copied to revealjs.template) has type issues in the scroll-view block that Quarto's template.html fixes. Tracked in jgm/pandoc#11486:

  • scrollSnap: '$scrollSnap$' renders false as string 'false' (truthy in JS)

  • scrollActivationWidth: '$scrollActivationWidth$' renders numbers as strings

  • scrollProgress defField defaults to true instead of reveal.js's 'auto'

Adding New Reveal.js Options

When adding support for a new reveal.js configuration option:

  1. Add the constant to constants.ts

  2. If the option needs YAML normalization (e.g., a compound structure), add to revealResolveFormat()

  3. If the option needs a default value, add to extras.metadata in formatExtras()

  4. If the option needs type-safe rendering, add to template.html with appropriate $if/$else$ guards

  5. If the option can only be handled post-render, add to extraConfig (last resort)

  6. Add smoke-all tests covering type edge cases