Which show rule takes precedence?

Here is how rules are applied for a particular element according to my understanding (copy-pasted from another thread but it better belongs here I guess):

  1. All applicable show-set rules are gathered, with later rules overwriting previous ones so

    #show ref: set text(red, weight: "bold")
    #show ref: set text(blue)
    

    will show refs in blue and bold.

  2. The element is materialized in accordance with these show-set rules. For example after

    #show ref: set ref(supplement: [X])
    

    all ref elements with unspecified supplement will be materialized with supplement [X].

  3. Normal show rules (i.e. functions, not show-set) are applied starting with the most local one. The rule functions are called with the materialized element as argument. Each rule is applied recursively to its own output, until the output contains nothing new that matches the show rule. For example

    #set math.equation(numbering: "(1)")
    #set ref(supplement: [0])
    #show ref: it => {
      let i = int(it.supplement.text)
      if i < 3 {
        let new = ref(it.target, supplement: [#(i + 1)])
        return [{it: #it, new: #new}]
      }
      [{final: #it}]
    }
    
    $ x = y $ <eq>
    See @eq.
    

    produces
    image

    Indeed the first time the show rule is applied, it returns the content {it: #it, new: #new}. The rule is then applied again on the new ref, but not on #it as that has already been processed. This is repeated until the new ref has supplement [3]: then the show rule simply returns {final: #it}. This output contains nothing new matching the show rule, so Typst moves on to the next (more outer) show rule.

Remarks:

  • Default values are used at the point where the element is materialized. If you change them afterwards it’s too late:

    // Works for future heading elements (they are not yet materialized)
    #show heading: set heading(outlined: false)
    
    // Doesn't work: the `it` heading is already materialized
    #show heading: it => { set heading(outlined: false); it }
    
    // Works: the content of the heading is not yet materialized
    #show heading: it => { set text(purple); it }
    
  • As explained by @bluss, a show rule applies to the element, but it can return something else than “just the element”. For example

    #show heading: it => { set text(green); [A(] + box(it) + [)a] }
    

    puts a style wrapper and box around every heading.

  • A show rule can replace the element with a new element of the same type:

    // Replace every level-1 heading with a level-2 heading
    #show heading.where(level: 1): it => heading(level: 2, it.body)
    

    The show rule can also replace the element with something completely different:

    // Replace headings with text `x`
    #show heading: [x]
    

    Note that a query will also find discarded elements. So in the first case above, query(heading) will return both the level-1 and level-2 headings. And in the second case it will return the heading that was replaced with [x].

5 Likes