How to override the heading show rule?

Full code here: https://typst.app/project/RKF3HKdEFeSxKdBw8Rni9b.

I have this sample template:

// template.typ
#let fill = (
  red,
  green,
  blue,
  aqua,
  teal,
  maroon
)

#let example(body) = context {
  set text(size: 9pt)

  show heading: it => {
    set text(fill: fill.at(it.level - 1))

    it
  }

  body
}

And this is my main Typst file:

#import "template.typ" : example

#show: example.with()

#grid(
  columns: (1fr, 3fr),
  rows: 1fr,
  fill: (silver, white),
  inset: 9pt,
  [
    #show heading: set align(center)
    #show heading.where(level: 3): it => {
      set block(
        fill: oklch(82.8%, 0.111, 230.318deg),
        inset: (x: 5pt, y: 8pt),
        radius: 2pt,
        width: 100%,
      )

      set text(size: 20pt, fill: yellow)

      it
    }

    = Level 1
    == Level 2
    === Level 3
  ],[]
)

This produces:

Why is the fill: yellow in set text(size: 20pt, fill: yellow) not overriding the default fill from the template?

The problem was due to how show rules work, which is more complex than I thought.

Using a toggle state in the show rule works:

// template.typ
#let use-sidebar-style = state("sidebar-style", false)

#let example(body) = {
  show heading: it => context {
    let fill = if use-sidebar-style.get() == true { red } else { blue }
    set text(fill: fill)
    it
  }

  body
}

// main.typ
#import "template.typ" : example, use-sidebar-style
#show : example.with()
#set page(paper: "a6")


== 123
#use-sidebar-style.update(true)
== 123
#use-sidebar-style.update(false)
== 123

Which in turn produces:

I suggest you have a look at Which show rule takes precedence? - #5 by sijo

Elaborating a bit on the specific problem here: Show/set rules behave differently than show rules with anonymous functions. What I mean is that

#show heading.where(level: 1): it => {
  set text(fill: fill.at(0))
  it
}

is different from

#show heading.where(level: 1): set text(fill: fill.at(0))

The former is known as a transformational show rule and cannot be overridden by later show rules, because later show rules do not “know” what happens inside the function. The latter case is much simpler for typst to handle and therefore allows you to override it later.

This is mentioned in the show rule documentation, but admittedly it is quite hidden especially if you don’t know what you are looking for:

So, we can replace your original heading coloring rule by

show heading.where(level: 1): set text(fill: fill.at(0))
show heading.where(level: 2): set text(fill: fill.at(1))
show heading.where(level: 3): set text(fill: fill.at(2))
show heading.where(level: 4): set text(fill: fill.at(3))
show heading.where(level: 5): set text(fill: fill.at(4))
show heading.where(level: 6): set text(fill: fill.at(5))
Alternatively we could use a function instead of setting each level manually, but I find this less readable:
let body = fill.enumerate().fold(body, (body, (index, color)) => {
  show heading.where(level: index + 1): set text(fill: color)
  body
})

And with this, you are able to override the color in the later show rule:

For the same reasons as above, you should use show/set rules in the other definition as well
#show heading.where(level: 3): set block(
  fill: oklch(82.8%, 0.111, 230.318deg),
  inset: (x: 5pt, y: 8pt),
  radius: 2pt,
  width: 100%,
)
#show heading.where(level: 3): set text(size: 20pt, fill: yellow)