How to structure problem sets with optional answers?

I’ve been spinning my wheels on this for a while now. I’m using the command line version of typst and trying to set up a system of imports/includes for writing problem sets.

Each problem set should be its own file (call it pset.typ), and each problem that appears in the problem set should be its own file (say p1.typ, p2.typ, etc). So far no problem, just use #include "p1.typ" in the main file.

Each problem file p1.typ should also include a solution that is shown or not depending on a variable (call it show_solns) set in pset.typ. Here, #include breaks down because it is evaluated before returning content to the main file. I’ve gotten around this with something like:

#eval(read("p1.typ"), 
    mode: "markup", 
    scope: (show_solns: show_solns)
)

I don’t want to write this out for every problem in the problem set, so I could wrap it in a function. However, I don’t want to define this function in every problem set I write, so it makes sense to define it in a library file that gets #imported, but then I run into all sorts of issues with paths being relative to the imported file, not the problem set.

Additionally, I don’t want to have to recreate the show/no show logic in every problem. Potential solutions are defining a solution function that gets imported to each problem, or perhaps importing something that shows/doesn’t show a section if its heading is == Solution.

On top of it, I’d like to import the same library for a bunch of different classes that live all over the filesystem.

All of this is super simple in latex, with the use of \input{}, however that doesn’t exist in typst and the developers have made it clear that they will not support it. Which is frustrating for people like me that aren’t programmers and just want to get stuff done in a markup language.

This is a minimal solution for creating problem sets in Typst with optional solutions that can be shown or hidden using a state variable. The system consists of just 3 files and solves the common issue of wanting to generate both student and instructor versions of problem sets.

The Problem

In LaTeX, you could easily use \input{} to include problems and control solution visibility. Typst’s #include doesn’t work the same way because it doesn’t pass scope to included files. This system provides a clean solution using Typst’s state management.

The Solution

1. Library File: lib.typ

This file contains the core functionality - a state variable and the solution function.

// Simple problem set library
#let show-solutions = state("show-solutions", false)

#let set-solutions(show_) = {
  show-solutions.update(show_)
}

#let solution(body) = {
  context {
    if show-solutions.get() {
      block(
        fill: rgb("#f0f8ff"),
        inset: 10pt,
        radius: 5pt,
        width: 100%,
        [
          *Solution:*
          #body
        ]
      )
    }
  }
}

2. Main File: problemset-einfach.typ

This is your main problem set file. Change true to false to hide solutions.

#import "lib.typ": *

// Show solutions? true or false
#set-solutions(true)

= Problem Set 1

#include "beispiel-problem.typ"

3. Problem File: beispiel-problem.typ

Individual problem files. Each imports the solution function and can use it.

#import "lib.typ": solution

== Problem 1: Quadratic Equation

Solve the equation: $x^2 - 5x + 6 = 0$

#solution[
  $x^2 - 5x + 6 = (x-2)(x-3) = 0$
  
  Therefore: $x = 2$ or $x = 3$
]

Usage

  1. With solutions (instructor version): Set #set-solutions(true) in your main file
  2. Without solutions (student version): Set #set-solutions(false) in your main file
  3. Add new problems: Create new .typ files with #import "lib.typ": solution and use #solution[...] blocks

Benefits

  • Simple: Only (minimum) 3 files needed
  • Flexible: Same problem files work for both versions
  • Scalable: Add as many problems as needed
  • Clean: No complex path management or eval() calls
  • Reusable: Library can be used across multiple problem sets

This system provides a similar functionality as LaTeX’s \input{} but works within Typst’s design philosophy.

Alternative: Using as a Local Package

If you encounter path issues when using the library across different directories, you can set up the library as a local Typst package. According to Typst’s documentation, you can create system-local packages.

1. Create Package in the Local Package Directory

For local development, create your package in your local Typst packages directory (depending on your operating system):

..typst/packages/local/problemset/0.1.0/
├── typst.toml
└── lib.typ

2. Package Configuration: typst.toml

[package]
name = "problemset"
version = "0.1.0"
entrypoint = "lib.typ"
authors = ["Your Name"]

3. Library File: lib.typ

// Simple problem set library package
#let show-solutions = state("show-solutions", false)

#let set-solutions(show_) = {
  show-solutions.update(show_)
}

#let solution(body) = {
  context {
    if show-solutions.get() {
      block(
        fill: rgb("#f0f8ff"),
        inset: 10pt,
        radius: 5pt,
        width: 100%,
        [
          *Solution:*
          #body
        ]
      )
    }
  }
}

4. Usage with Package Import

Main file:

#import "@local/problemset:0.1.0": *

#set-solutions(true)

= Problem Set 1

#include "problem1.typ"

Problem file:

#import "@local/problemset:0.1.0": solution

== Problem 1: Quadratic Equation

Solve: $x^2 - 5x + 6 = 0$

#solution[
  $x^2 - 5x + 6 = (x-2)(x-3) = 0$
  
  Therefore: $x = 2$ or $x = 3$
]

This approach ensures the library works consistently across different directory structures and can be reused across multiple courses or projects. For more details on local packages, see the Typst packages repository.

2 Likes

Hi @k2enemy, thank you for your question! Could you maybe try to revise your post’s title to be a complete question as per the question guidelines:

Good titles are questions you would ask your friend about Typst.

In this case in particular, a reader doesn’t have a good impression what kind of advice you’re looking for. I think a good title could be something like “How can I make a configuration option available to an included file?” – but feel free to come up with your own title.

We hope by adhering to this, we make the information in this forum easy to find in the future. Thanks!

Thank you, this was very helpful!