Typst font-size headings inheritance

The problem

Relative sizes are scaled by the heading’s own native size. In H1 (1.4x), 1.5em becomes 21pt (1.4 × 1.5 × 10 pt). In H2 (1.2x), 1.5em becomes 18pt (1.2 × 1.5 × 10 pt).

show heading: h => {
   let relative-size = 1.5em
   block(stroke: red)[
      Heading level #h.level
      Text in 1.5em: #text(size: relative-size)[ABC]
   ]
}

The workaround

Applying 1em / scale to recover the document’s true base size (10pt). Now, 1.5em correctly becomes 15pt (1.0 × 1.5 × 10 pt) everywhere.

show heading: h => {
   // Define Typst's default scale factors
   let scale = if h.level == 1 { 1.4 } else if h.level == 2 { 1.2 } else { 1.0 }
   // Apply inverse scale to neutralize the heading's influence
   set text(size: 1em / scale)
   let relative-size = 1.5em
   block(stroke: green)[ ... ]

Conclusion & open question

This behavior creates a challenge when designing consistent UI elements (like info boxes, navigation aids, or automated headers) that are triggered by headings. Since these elements often rely on relative units (em) for consistent internal spacing and typography, their visual appearance shifts depending on the heading level that invokes them.

While the inverse scaling workaround works, it requires hardcoding Typst’s internal scale factors. Is there a more idiomatic way to “escape” the local heading’s style context and evaluate content relative to the document’s root font size from within a show rule, without forcing absolute lengths?

I think you’re looking for a way to revoke default rules for heading elements. There’s an open issue for that, and a related bullet point on the roadmap.

I don’t think there’s currently a better solution than manually undoing the header style. But a more idiomatic way to do this would be

#show heading.where(level: 1): set text(1em/1.4)
#show heading.where(level: 2): set text(1em/1.2)

You can put this at the top of your document/template, or make it its own small template.

2 Likes

Thank you, very useful answer!

1 Like