How to override styles?

I was messing about with typst styling rules while I was trying to get a specific style. What I had in mind was to apply a certain style to all the text before the first header and a different style to all the text after the first header. Here is an example I set up with my own crude implementation.

#let color-state = state("color", false)

#show heading: it => {
  set text(fill: primary-red)
}
#show par: it => {
  context {
    let is-action = color-state.get()
    let txt-color = if is-action { black } else { red }
    set text(fill: txt-color)


    block(
      text(it.body)
    )
  }
}

#show heading.where(level: 4): it => block[
  #text(it.body, size: 1.3em, fill: red, weight: "light")
  #v(-0.5em)
  #line(length: 100%, stroke: 1pt + red)

  #color-state.update(true)
]

This is some text before the first header, which needs to be red and is red

==== This is the first header that needs to be red and is red

This is the text after the first header, which needs to be black and is black

==== This is the second header that also needs to be red but is black

It all works good and well, maybe even too good. I want the headings themselves to be a different style (in this case just red) but I couldn’t get the show rule for headings to override the show rule for paragraphs. I tried putting it before, after and inside to no avail.

The problem arises because you are stripping away the heading element when you use #text(it.body, ...). This makes the show rule return a par element, which matches with the other show rule. Simply keeping the entire heading, i.e. #text(it, ...) fixes the issue.

Also, you should prefer using show-set rules whenever possible, as this allows for more customisation later (see: Is it possible to call set multiple times in a single show rule? - #8 by ensko)

In your example, this would be

#show heading.where(level: 4): set text(size: 1.3em, fill: red, weight: "light")
#show heading.where(level: 4): set block(below: 0.7em)
#show heading.where(level: 4): it => {
  show divider: set line(length: 100%, stroke: 1pt + red)
  show divider: box
  it + divider() + color-state.update(true)
}

So that later in the document, if you wanted to e.g. change the line to asterix’s you could just write

#show heading.where(level: 4): it => {
  show divider: set align(center)
  show divider: block[\* \* \*]
  show block: set text(fill: red)
  it
}

Instead of re-writing the whole show rule.

1 Like

Changing it.body to just it causes the whole text to disappear for some reason. So having:

#let color-state = state("color", false)

#show heading: it => {
  set text(fill: red)
}

#show par: it => {
  context {
    let is-action = color-state.get()
    let txt-color = if is-action { black } else { red }
    set text(fill: txt-color)


    block(
      text(it.body)
    )
  }
}

#show heading.where(level: 4): it => block[
  #text(it, size: 1.3em, fill: red, weight: "light")
  #v(-0.5em)
  #line(length: 100%, stroke: 1pt + red)

  #color-state.update(true)
]

This is some text before the first header, which needs to be red and is red

==== This is the first header that needs to be red and is red

This is the text after the first header, which needs to be black and is black

==== This is the second header that also needs to be red but is black

Results in this:

I will try to remember your advice about prioritizing show set rules, but first I need to figure this out.