Three outputs from one source#
The talk for the canvas, the deck to read afterwards, and the handout to write on, without a second version to keep in step.
The slide deck#
The PDF run without further arguments gives one page per slide, in the size of the canvas. Every element that moves in the browser stands in its final state: what is revealed is there, and where several versions share one place, the last one. Notes, transitions and bridge jobs produce no output and fall away by themselves.
The handout#
One argument turns the deck into a handout on A4:
#show: presentation.with(handout: 3) // three slides per pagehandout takes true (two per page) or a number from 1 to 6 and applies only to the PDF. The slides are not typeset again, only made smaller, so a handout cannot differ from what stood on the canvas.
Beside or below each slide stands its note; where a slide has none, ruled lines take its place. Up to two slides per page the notes stand below and the slide takes the full width; from three on they stand beside. Under a slide at least four lines remain: a 4:3 slide that would otherwise take the whole height at two per page is made narrower for them.
Bookmarks#
The PDF carries an outline, like any other Typst document: one entry per slide, sections above them, the title slide at the top. In a reader that is the sidebar you jump with instead of paging.
None of it is visible. Each slide places a heading that hide strips of ink and place takes out of the flow; it exists only to be bookmarked. The detour is needed because the headings that cut the deck into slides become dictionaries on the way and never reach the document, so without the silent heading there would be nothing for Typst to build an outline from.
A heading a deck writes inside a slide body gets no bookmark of its own. It would otherwise sit beside the slide’s own, and under pages: "step" once per step page as well – the same name several times, on pages that do not show it. A slide without a title stays out of the outline; an empty entry is worse than none.
What the paper leaves out — and what to plan for#
| On the slide | On paper |
|---|---|
anim, stagger, #pause | everything visible, in the same place and the same room |
alternatives | the last version only, in the shared box |
morph | the content of each slide – the chain becomes the calculation |
embed, geogebra | fallback, otherwise a placeholder with label; link below it |
video | the poster, otherwise a grey panel |
flipbook | a single picture: still or render(0.0) |
scene | a single picture: still or the last stop |
speaker-note | beside its slide in the handout, nothing in the plain slide deck |
transition, bridge-job | nothing – they belong to the motion alone |
fallback and link while writing the slide. Afterwards every embedded place has to be visited a second time.All three in one run#
Since Typst 0.15 one compilation can write several files. bundle writes talk, deck and handout at once:
#bundle(
theme: themes.lesson,
title: [Completing the Square],
handout: "handout.pdf",
)[
= A section
== A slide
Text.
]typst compile --features bundle,html --format bundle talk.typ outhtml, slides and handout are file names, none leaves that output out, and per-sheet is the number of slides on a handout page. Everything else goes to presentation unchanged.
What the package looks up stays in its own output. The counters start afresh per output, those of figures and equations and a deck’s own included, and a link such as an entry of contents() leads into its own file. Two things Typst keeps across the whole bundle, though, out of the package’s reach. A deck’s own state carries on from one output into the next, since there is no value it could fall back to; a running number therefore belongs in a counter. And a label stands once in every output: a reference such as @fig stops the bundle with “label occurs multiple times in the document”, even where the deck compiles on its own, and an outline(target: figure) of the deck’s own lists the figures of every output.
With pages: "step" there is one limit more. Where a reveal then sits in a version of alternatives or a stage of build and another slide follows – #alternatives([A], [#anim[x]]) –, the bundle warns that it does not converge, and in talk.html the x never comes. Such a deck builds its HTML on its own, with html: none in the bundle.
--features bundle,html. A file that uses bundle compiles only with --format bundle; a plain typst compile talk.typ talk.pdf stops with “constructing a document is only supported in the bundle target”. To keep both routes open, put the body in a #let and call presentation by hand.Notes#
speaker-note files a note with the slide. It stands in the body or as the argument note on slide:
== The Pythagorean Theorem
#speaker-note[Show the dissection first, then the formula.]The note appears in the speaker view and on the handout. It produces nothing in the deck PDF.
A note has to carry text: the speaker view transports it as a string and the handout prints it where there is text. A note built purely out of layout – a fit, a bare rect, an image – is refused with a message. What is meant to be seen belongs on the slide.
Two clocks for the class#
t starts the full-screen clock. It covers the slide edge to edge, with digits the back row can read: the room is on a break. Paging ends it and uncovers the slide again.
⇧T starts the pinned clock. It stands on the slide and leaves the task underneath in place, so paging deliberately does not end it. In the presenter view it takes the mouse: a drag in the middle moves it, a drag at the edge makes it larger or smaller, and the cursor says which of the two a drag would do. Place and size travel along to the talk window as fractions of the stage, so the clock stands in the same spot of the slide and at the same size, in a window of a different size.
Both ask for the minutes first and only then run. ⇧← and ⇧→ give a minute more or less; the same key again ends the clock.
What a deck knows about the pinned clock it writes with class-clock:
#slide[
= Group work
#class-clock(12)
Find three examples in pairs.
]Nothing starts from that: ⇧T offers the twelve minutes and the speaker confirms or changes them. The deck knows how long the task was meant to take, the room decides how long it gets.
The digits set the clock#
A question at the start of the lesson, a minute of talking in pairs: the hand is on the keyboard anyway, and a number is shorter than t, field, number, Enter. 3 starts three minutes, 7 seven, 0 ends it again.
What starts is the pinned clock. The question stays on the slide while the time runs, and paging does not end it. And it works without a second window: one machine at the beamer is enough. The clock used to be reachable only from the desk, which is not the arrangement anyone teaches in.
On a slide with a cue() group the digits belong to the group. That holds for the whole slide, not for the single keystroke: a digit the group does not have, and a second press on the same point, start no clock there either. A slide belongs either to the points or to the clock, and the slide itself says which.
b blacks the hall out and leaves the pinned clock standing: blacking out during group work takes away the distraction, not the time. The full-screen clock still gives way – it covers the hall in any case.
How calmly the clock reads#
A clock that jumps every second pulls the eye off the task each time. room sets the step for the whole deck:
#show: presentation.with(
room: (clock: (step: 5)), // the number moves only every five seconds
)The last step still counts down singly – 00:15, 00:10, 00:05, 00:04, 00:03, 00:02, 00:01, 00:00. A clock that shows 00:00 for a full five seconds while time is left sends the class home early.
The step has to divide 60 evenly: 1, 2, 3, 4, 5, 6, 10, 12, 15, 20, 30 or 60 seconds; duration(seconds: 5) works in place of the number. One that does not is refused at compile time:
#show: presentation.with(room: (clock: (step: 7)))Otherwise the number is already wrong the moment it starts – a class-clock(1) would read 00:56 at a step of seven seconds, and that reads like a fault of the clock rather than one of the setting.
On the deck and not on the slide, deliberately. How coarsely the clock reads is a property of the eye and not of the task; a running clock that changed its rhythm on paging would look like a fault. How long a task is meant to take does genuinely differ per slide – that is what class-clock is for.
room: (clock: (digits: false)) gives the digits back; whoever drops the clock entirely writes speaker-view: (clock: false) and loses the digits with it.
A sound on a key#
A signal the class knows: time is up, pack away. room binds a key to a sound file.
#show: presentation.with(
room: (sounds: (a: "airhorn.mp3", g: "gong.mp3")),
)The file travels beside the HTML like any other media file; the package ships no sound of its own. The example decks tour and unterrichten carry a horn computed for them rather than recorded, examples/medien/airhorn.mp3, with the command that builds it in PROVENANCE.md beside it. It is heard in the hall and only there: the speaker sits at the machine, the speakers are in the room, and the sound is never heard twice. The key may be pressed in either window.
Free letters are a g h i j k p q s u v w y – the rest belong to the runtime, and a taken one is refused at compile time with the free ones listed:
#show: presentation.with(room: (sounds: (b: "gong.mp3")))The chosen keys appear in the speaker view’s key bar, since the translated help text cannot know them.
If the file is missing, the runtime says so at load time rather than when somebody presses the key: a missing image leaves an empty rectangle, a missing sound leaves nothing.
The dot in the hall#
The pointer’s dot, from “The pointer” above, is on by default and needs nothing said about it. room has it in case the default does not suit the deck:
#show: presentation.with(
room: (pointer: (color: rgb("#00c853"), size: 4%)),
)size is a ratio of the slide width and not a length, because the slide copy in the speaker view and the canvas in the hall are measured in different numbers of pixels – 622 against 1600 on this machine – and the dot is meant to be the same size on the slide in both. It has to sit between 0.8% and 6%: below that the two rings are thinner than a pixel on the wall, and they are the ones carrying the contrast – at 0.8% of a 1600-pixel stage each of them is only 0.9 pixels thick. Above it the dot covers a line of text.
#show: presentation.with(room: (pointer: (size: 12%)))The colour is free, and freer than it looks: the dot carries a light ring and a dark one around its core, and whichever ground it lands on, one of the two cuts it out. Measured, the dark ring reaches 10.90 on a light slide and the light one 15.45 on themes.night, whatever the core is. One method for both numbers, so that two measurements of one thing do not read as a contradiction: the dot stands at the middle of the stage in a 1600 by 900 hall window at its default size, the screenshot is read unscaled, the ground is the most frequent colour on the circle of two and a half radii around the centre, the two rings are the most frequent colours at 0.57 and 0.71 radii, computed after WCAG 2.1. The light slide is the default paper, #fafafa. A green of one’s own reads 2.14 in the core by the same method and keeps the dark ring’s 10.90 all the same. A badly chosen colour therefore costs visibility, not legibility. Without a colour of its own the dot takes the deck’s accent.
pointer: false takes the dot away entirely:
#show: presentation.with(room: (pointer: false))Embedded frames stay operable: they are the pointer mode’s other half and do not hang off the dot. What comes back with false is the old note – on a slide with nothing embedded, the speaker view now says there is nothing to point at, because now that is true again.