A ✓ (or ✔ / #yes) marks the correct option; option order shuffles per set. #explain shows only in the key.
+ #m(2) Which option is correct? - ✓ This one - Not this one #explain[Key-only explanation.]
Two or more ✓ options make a multi-select — “(Select all that apply.)” is added and the key reports all letters (e.g. AB).
+ #m(2) Which reduce overfitting? - ✓ Dropout - ✓ Weight decay - More parameters
The answer lives inside the blank: hidden on the paper, printed in the key, exported to the grading CSV. Math answers work.
+ #m(1) Water is H#sub[2]#blank[O], and $e^(i pi) = #blank(width: 1.5cm)[$-1$]$.
answer(space) prints writing space on the paper; the model answer and rubric appear only in the key.
+ #m(3) Derive the normal equation.
#answer(4cm, rubric: [+2 gradient; +1 solve.])[
$hat(theta) = (X^T X)^(-1) X^T y$]
answer(none) prints no space — for exams answered in a separate booklet. Quiz-wide default: quiz.with(answer-space: none).
+ #m(2) Answer in your booklet. #answer(none)[Model answer, key only.]
“None/All/Both of the above” pins itself last automatically; #pin / #pin-first pin anything else under shuffling.
+ #m(1) What is the output size? - $30 times 30$ - $32 times 32$ - ✓ None of the above // auto-pinned last
Short options in two columns to save vertical space.
+ #m(1) #opts(columns: 2) Which direction? - ✓ North - South - East - West
Options flow inline in one wrapping row — the densest layout.
+ #m(1) #opts(compact: true) $2 + 2 = $? - $3$ - ✓ $4$ - $5$
I/II/III progressions must not shuffle.
+ #m(1) #opts(shuffle: false) Which are true? - I only - ✓ I and II - II and III
Question identity defaults to a hash of its own text; #qid freezes it so wording edits never reshuffle its options.
+ #m(1) #qid("conv-output") Editable wording,
stable shuffle.
- ✓ Stable
- Equally stable
A part whose questions stay in authored order in every set.
= Long Answers #section(shuffle: false) + First, always first. #answer(3cm)[...]
header:/footer: accept auto (generated), none, fixed content, or a function of (exam, set, mode, total). Totals and part subtotals are always computed.
#show: quiz.with(
id: "quiz-1", sets: ("A", "B"),
header: none,
footer: info => align(center,
text(9pt)[Set #info.at("set") · #info.total marks]),
)
The constructor front-end: build banks with mcq()/fib()/subj(), filter by type/topic/difficulty, and pick N — the same N in every set.
#make-exam(
exam: (id: "midsem", sets: ("A", "B")),
questions: bank,
sections: ((title: "MCQ",
use: ("must-appear",), // guaranteed
filter: (type: "mcq"),
pick: 10),), // same 10 in every set
)
All of the snippets above live in one real randomized exam (exams/demo-features.typ). Left: the student paper. Right: the same pages of its answer key.