Styling emphasised text consistently in quarto markdown with pandoc lua filters

How pandoc spans & filters help me use emphasise styles like boldface and italics consistently throughout my documents.
pandoc
quarto
markdown
Author

Cynthia Huang

Published

August 7, 2025

Modified

August 14, 2025

Problem: Not so straightforward text emphasis

Writing interdisciplinary papers often requires referencing terminology from multiple fields, clarifying distinctions, and sometimes even defining new terms to capture additional nuances. Naturally, it makes sense to use typographic emphasis and/or punctuation to highlight ‘confusing’, new, or important terms — and help readers keep track of them. However, during the process of writing, it’s easy to lose track of choices I (or co-authors) have made about the ‘meaning’ of particular formatting choices.

For example, while writing a paper about assessing the statistical quality of web-scraping, I wanted to distinguish between explicitly defined terms, where specific definitional nuances and distinctions were particularly important to the rest of the paper, and references to other terms that were either less important, already had well-established definitions, or would be defined later on.

Of course, when explained like this it seems straight forward to style these terms using the standard markdown syntax of **boldface** and _italics_. However, I often forgot or mixed up which style I was using for each case. I also ended up adding a third type of emphasis at some point: quotation marks for ‘confusing terms’. These terms had some significant definitional conflict or ambiguity in usage between different disciplines that needed addressing.1

Long story short, it turns out a simple “find and replace **term** with _term_ wasn’t sufficient to achieve consistency across the paper. But my brain also couldn’t just ignore it… Luckily, I knew just enough about pandoc and quarto extensions that I thought “pandoc bracketed spans and lua filters can probably solve this!”.

Solution: Pandoc lua (and LLMs) to the rescue

Even more fortunately, most LLMs can write lua much better and faster than I can (which isn’t difficult when I have written basically zero lua ever). So, I asked Claude to:

Please write a pandoc lua filter which styles spans with the classes .def in italics, and .term in boldface italics.

And voila! I had a pandoc filter that I could call in my .qmd file that would consistently style all text with the same class (across both pdf and html formats). Here’s what that looked like:

Directory structure

paper/
├── example.qmd
└── style/
    └── emphasis-spans.lua

Example Document

example.qmd
---
title: Paper with consistent emphasis styles 😎
filters:
- style/emphasis-spans.lua
---

Here was a [definition]{.def}. And here was a [term]{.term}.

Lua filter

This is the lua script specified in yaml above under the filters: argument. It gets called during the rendering process and handles the formatting magic.

style/emphasis-spans.lua
-- A filter to style spans with .def and .def-emp classes independently

function Span(el)
    -- Check for .def first (bold italics)
    if el.classes:includes("def") then
      el.content = {pandoc.Strong(el.content)}
      return el
    end

    -- Then check for .term (italics)
    if el.classes:includes("term") then
      el.content = {pandoc.Emph(el.content)}
      return el
    end

    return el
  end

What even are Pandoc Spans & Filters?

Bracketed spans let you add classes and attributes to bits of inline text using this syntax: [inline text]{.class style="red"}2. In general, the effect of the class and attributes depends on what output format you’re rendering to – i.e. when rendering to HTML the example in the previous sentence is parsed into <span style="color: red">some text</span>. This requires the output format you’re parsing to to know what your classes and attributes mean – i.e. no styles are applied to inline text when rendering to pdf via LaTex. This is where pandoc lua filters come in.

Exactly how filters work requires some understanding of how pandoc works, which really isn’t that necessary for this post to be useful. But basically, the pandoc.Strong() and pandoc.Emph() above constructors handle generating the correct HTML, latex or other format syntax to apply each style of emphasis in the corresponding output format. Other inline constructors include .SmallCaps() and .Strikeout() spans3.

The combination of spans and filters means I can easily change my mind later about how to style words that I had enclosed in each type of span (e.g. modifying or swapping the styling of terms with the .def and .term classes). In fact, you can do all sorts of cool things once you’ve wrapped up things in these special spans – including generating a glossary of all terms with a particular class4.

Key Takeaways

  • Inconsistent typographic emphasis makes me sad, (and hurts to try and manually correct)
  • Pandoc lua filters and bracketed spans help by separating the choice of which text should all have the same styling (using spans), and defining what that styling should be (using pandoc constructors).
  • LLMs can help you write lua filters so don’t be scared!

Footnotes

  1. reading over the paper again, it seems some other inconsistencies also escaped my notice… but that’s a different Cynthia’s problem.↩︎

  2. See this post by Mine Çetinkaya-Rundel for examples in Quarto: Spans, A Quarto tip a day↩︎

  3. See the Pandoc Lua Filters documentation for more details: https://pandoc.org/lua-filters.html#module-pandoc↩︎

  4. Andrew Bray’s quarto extension: andrewpbray/glossary implements exactly this!↩︎

Citation

BibTeX citation:
@online{huang2025,
  author = {Huang, Cynthia},
  title = {Styling Emphasised Text Consistently in Quarto Markdown with
    Pandoc Lua Filters},
  date = {2025-08-07},
  url = {https://www.cynthiahqy.com/posts/emphasis-spans-pandoc/},
  langid = {en}
}
For attribution, please cite this work as:
Huang, Cynthia. 2025. “Styling Emphasised Text Consistently in Quarto Markdown with Pandoc Lua Filters.” August 7, 2025. https://www.cynthiahqy.com/posts/emphasis-spans-pandoc/.