Importing with parameters, or switchable templates

I have quite a few documents which use fundamentally the same template, but with minor changes (e.g. a different colour scheme, font, or logo).

At the moment, my projects import template.typ for general settings. template.typ then imports assets.typ where I store these variable settings (e.g. defining which image is “Logo” or which font to use). I am having to use separate projects for each theme.

This looks something like:

///IKEAexample.typ
#import "template.typ": *

#show: project.with(
  documentname: "IKEA thingy",
  authors: "Hurtleberry",
  pubyear: "2025"
)

...(and then the main document content)

///template.typ

#import "assets.typ": *
#import "ogl.typ": *
#import "blocks.typ": *

...(all the other stuff in the template)

///assets.typ

#let logo = "IKEA.png"
#let coverimage = "IKEAcover.jpg"
#let logowidth = 1cm
#let logoheight = 1cm
#let bodyfont = "Barlow"

I would like to be able to put all these documents in a single Typst project, and tell it to pick a group of settings based on the theme. So I could flag document A as being IKEA-themed, document B as NHS-themed, and so on to have the correct logos, fonts, and colour schemes turned on.

To me, the natural approach involves setting a parameter in the document (#let theme = “IKEA”), grouping the settings in #if statements, and then either passing the parameter to the #import, or having the import simply use the value I’ve already defined. e.g.

///in exampledocument.typ
#let theme = "IKEA"
#import template.typ

///in template.typ
#if theme = "IKEA" [
  #let logo = "IKEAlogo.png"
  #let coverimage = "IKEAcover.jpg"
  #let logowidth = 1cm
  #let logoheight = 1cm
  ]
else
  [
  #let logo = "NHSlogo"
  #let coverimage = "NHScover.jpg"
  #let logowidth = 2cm
  #let logoheight = 2cm
]

However, the variable is not accessible by the files I’m importing, so I get unknown variable errors. As usual I’m probably missing something obvious :sweat_smile:

Is there a way to do this, or more generally, to achieve this idea of having different themed sets within a project that can be called on by individual documents?

2 Likes

I’m sorry, I don’t understand your point. For what it’s worth, I’ve never used LaTeX and I don’t run anything in Bash Shell - in fact I’m not even sure what that is.

It does look like there are some similarities in the question you linked, and I don’t really understand the answers there either. I’ll try reading through it again when I’m less tired :smile:

Important to understand here is that typst variables are scoped to the enclosing block or file, and cannot be accessed from other files.

So in the following snippet:

#let theme = "IKEA"
#import "template.typ"

the code in the template.typ file is executed independently from the importing script, and only has access to variables defined or imported inside that file.


If the “theme” variables are only needed inside the template [1], the best solution is to simply pass the “theme” as an argument to the template function:

// main.typ
#import "template.typ": *
#show: template.with(
  theme: "Wikidata",
)

// template.typ
#let template(
  theme: "IKEA",
  doc
) = {
  let themed = if theme == "IKEA" {(
    logo: "IKEAlogo.png",
    coverimage: "IKEAcover.jpg",
    logowidth: 1cm,
    logoheight: 1cm,
  )} else if theme == "Wikidata" {(
    logo: "Wikidata.svg",
    coverimage: "NHScover.jpg",
    logowidth: 4cm,
    logoheight: 2cm,
  )}

  [Templated!]

  // use themed
  image(width: themed.logowidth, height: themed.logoheight, themed.logo)
}

Alternatively, you could also store all these variables in state, but that can slow down compilation so for this I’d try to avoid it


  1. I.e. you don’t include the logo in your main text, and that is handled by the template directly ↩︎

2 Likes

Thank you! This way of doing this is very new to me so I appreciate the help very much.

This seems very close to doing what I want, and the problem isn’t with this at all; it’s a knock-on effect.

I have another bit of code which places logos next to certain bits of text. I’d finally got that apparently working. However, moving the logo size definitions into the template function means that no longer works as it can’t access the sizes.

I’ve tried moving the logo-placing function inside the template so it can access the logowidth etc., but then it’s no longer visible to the function calls in the main document. Feels like a bit of a catch-22. Do you have any suggestions?

If absolutely necessary I can hardcode the logo width to a single size and just deal with it, so it’s not a disaster if we don’t have a solution.

In that case using state might be easiest. Example:

///// template.typ
#let themed = state("themed")

#let template(
  theme: "IKEA",
  doc
) = {
  let _themed = if theme == "IKEA" {(
    logo: "IKEAlogo.png",
    coverimage: "IKEAcover.jpg",
    logowidth: 1cm,
    logoheight: 1cm,
  )} else if theme == "Wikidata" {(
    logo: "Wikidata.svg",
    coverimage: "NHScover.jpg",
    logowidth: 4cm,
    logoheight: 2cm,
  )}
  themed.update(_themed)

  [Templated!]

  // use _themed directly inside the template to avoid needing context
  image(width: _themed.logowidth, height: _themed.logoheight, _themed.logo)

  doc
}


///// main.typ
#import "template.typ": *
#show: template.with(
  theme: "Wikidata",
)

Outside of template:
#context image(width: themed.get().logowidth, height: themed.get().logoheight, themed.get().logo)

// or to reduce state access:
#context {
  let _themed = themed.get()
  image(width: _themed.logowidth, height: _themed.logoheight, _themed.logo)
}
1 Like

I think between these two you have solved all my issues, and made things much more elegant, so thanks!

1 Like

I created a thread earlier this year to discuss exactly the same question, I think:

Simple and complex solutions have been proposed. My personal conclusion for now is that (1) there is a missing feature in the Typst language to better support this, and (2) in absence of proper support, the solution which I suspect scales best is to have the package return a dictionary of names (constants and functions), instead of defining toplevel constants and functions directly.

Instead of

#import "template.typ": *

.... #logo ... #coverimage ...

users would do

#import "template.typ": template

// template is a dictionary with fields `logo`, `coverimage` etc.

... #template.logo ... #template.coverimage

Then if you want to add global parameters to your package, you can export a function that returns a dictionary. So users now write as follows:

#import "template.typ": template
// template is a function that returns a dictionary

#let template = template(theme: "IKEA")
// shadow the name with an instantiated dictionary

... #template.logo ... #template.coverimage

The change from one version to the other is smooth for users (unlike moving from the constants-at-toplevel to the packed-in-a-dictionary style, which requires a global change), so if people consistently package their stuff in dictionaries they can smoothly add parameters or in general more flexibility.

3 Likes

Importing a file requires double quotes, and the first example is missing the asterisk for importing everything from the module.

1 Like