How can I _very simply_ switch all headings' colors at arbitrary points?

This is driving me crazy :) I find it extraordinarily challenging to determine the absolute simplest way to specify that headings must be typeset in color, and switch to a different heading color at arbitrary points throughout the document.

My latest attempt was this:

#let mycolor = state("mycolor", red)
#mycolor.update(green)
#show heading: set text(fill: context mycolor.get())

= #lorem(4)
#lorem(100)

#mycolor.update(blue)

= #lorem(4)
#lorem(100)

Which fails for reasons I do not understand:

Tue Dec  9 17:32:17 CET 2025: ... error: expected color, gradient, or tiling, found content
  ┌─ temp2.typ:3:30
  │
3 │ #show heading: set text(fill: context mycolor.get())
  │                               ^^^^^^^^^^^^^^^^^^^^^

What am I missing? I just want to set things up so that I add a few lines of code wherever I want to change an accent/theme color that will apply to various types of content (including headings, as above).

The show-set rules are one-time rules, so after you apply it, it will not change anything. text.fill doesn’t take content, so you can’t inject dynamic content values. Which leaves you with show rule, that is applied for each element instance, but since it’s a callback/wrapper function, you can do dynamic stuff there, at the expense of the same show-set rules not working because of it.

#let mycolor = state("mycolor", red)
#mycolor.update(green)
#show heading: it => {
  set text(fill: mycolor.get())
  it
}

= #lorem(4)
#lorem(100)

#mycolor.update(blue)

= #lorem(4)
#lorem(100)

#let my-color = state("my-color", red)
#let heading-color(color) = my-color.update(color)
#set par(justify: true)
#show heading: it => {
  set text(fill: my-color.get())
  it
}

= #lorem(4)
== #lorem(5)
#lorem(100)

#heading-color(green)
= #lorem(4)
== #lorem(5)
#lorem(100)

#heading-color(blue)

= #lorem(4)
== #lorem(5)
#lorem(100)

2 Likes

Thank you so much, I had no idea about this distinction.

1 Like

@Andrew’s answer is great and gets your code working as you expect, but I can’t help but think the code so far is not “the absolute simplest way to specify that headings must be typeset in color, and switch to a different heading color”.

This comes to mind as the simplest:

= #lorem(4)
== #lorem(5)
#lorem(10)

#show heading: set text(green)
= #lorem(4)
== #lorem(5)
#lorem(10)

#show heading: set text(blue)

= #lorem(4)
== #lorem(5)
#lorem(10)

This may be simpler, but it does not create a unified, obvious way to change the color which is achieved by the heading-color(color) function.

2 Likes

Yeah, since I don’t know where else the mycolor state is used, I didn’t remove it. But if nowhere, then for sure it should be removed and just the show-set rule or its wrapper should be used directly instead.

1 Like

Another difference that matters is whether there are different scopes involved:

= #lorem(4)
== #lorem(5)
#lorem(10)

#include "chapter.typ"  // the color is changed in here

= #lorem(4)
== #lorem(5)
#lorem(10)

If chapter.typ changes the color by setting a state, then the subsequent heading is affected. If the file contains a show-set rule, it won’t be.

3 Likes

Why you wrote include as a function call?

whoops, oversight :slight_smile: