How can I create show rules in a loop?

Let’s say I have many words for which I want a show rule to treat them all with some transformation; say, I want them to all show up strong:

#show "foo": strong
#show "baz": strong

foo bar baz

In the real case, there are obviously potentially many more targets for the show rules; how can I create these show rules without repeating myself? Ideally I want my targets to be stored in arrays and somehow process the elements of the array – something like this:

#let words = ("foo", "baz")
#for word in words {
  show word: strong
}

foo bar baz

However, this approach does not work…


Similarly, I may have many math symbols of which I want to change the default behavior:

#show sym.lt: math.scripts
#show sym.lt.eq: math.scripts

$ a <=_X b <_X c $

Or something else; e.g. I want to make the content of several elements uppercase:

#show emph: upper
#show strong: upper

Uppercase _all_ the *things*!

Here’s an image of the show rules’ effects:

How can I solve the problem in these cases?

(Credit for this answer goes to @laurmaedje who wrote it here, here, and a few times earlier on Discord, and to @Eric for recommending fold().)


There is a general and a two more specific way to do this:

  • using a loop or equivalent to generate and apply multiple show rules
  • using regular expressions with alternatives (|)
  • using selector.or()

since the specific ways cover the most common use cases and are also better performance-wise, let’s start with these:

Regular expressions

The first two cases actually only concern strings. With "foo" and "baz" it’s obvious, but the symbols can also be easily converted to strings and are generally treated by Typst this way. That means we can construct a regular expression that handles all the values in a single show rule:

#let words = ("foo", "baz")
#show regex(words.join("|")): strong

foo bar baz

#let symbols = (sym.lt, sym.lt.eq)
#show regex(symbols.join("|")): math.scripts

$ a <=_X b <_X c $

selector.or()

The matches of a show rule are determined by a selector, and although Typst’s selectors can’t do everything, most cases where you want the same kind of show rules for multiple targets can be handled with or():

#let elements = (emph, strong)
#show selector.or(..elements): upper

Uppercase _all_ the *things*!

Regexes and strings can not be combined using selector.or(), so if you want to apply a rule to, say ("foo", emph), you’ll need to write two show rules; one with all the strings, the other with all the other targets.

Actually generating show rules in a loop

In case even that is not enough, there is still a way to actually generating show rules in a loop. First of all, what’s the problem with the original approach?

#let words = ("foo", "baz")
#for word in words {
  show word: strong
}

foo bar baz

Each show rule only applies until the end of the scope it appears in, and that is the current iteration of the loop. There is no content in the loop, so each show rule applies to nothing.

The solution is to apply the same principles shown in the Making a Template tutorial, just a bit more creatively. First, we move the (still manually duplicated) rules into a “show everything” rule:

#show: body => {
  show "foo": strong
  show "baz": strong

  body
}

foo bar baz

We now have the rest of the document in a variable body which we can work with. Now, one solution is using a loop and replace body with something that has one extra show rule applied in each iteration:

#let words = ("foo", "baz")
#show: body => {
  for word in words {
    body = {
      show word: strong
      body
    }
  }
  body
}

foo bar baz

This works, but it can be slightly shorten by using the array.fold() method which is perfectly suited for this situation:

#let words = ("foo", "baz")
#show: body => elements.fold(body, (body, word) => {
  show word: strong
  body
})

foo bar baz

As mentioned above, this is not great for performance. Regardless of how you do it, having a large numbers of show rules active at the same time is not great. It’s usually okay if every show rule is written out, but creating them in a loop can hide how expensive a piece of code is. Whenever possible, the approaches above are preferable.

3 Likes