Skip to content

About

A typst template for posters - Michiel style

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

2 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Conference poster template (Typst)

An A0 poster in one self-contained Typst file. No typst.toml, no packages, no network — it compiles offline on a clean machine with Typst 0.13+.

Fork this repo, then:

typst watch poster.typ poster.pdf

Edit two regions of poster.typ and nothing else — the CONFIG block at the top and the CONTENT section at the bottom. Everything between them is machinery. Drop your figure exports in figures/, keeping the shipped filenames or changing the paths in the config.

poster.typ        the file you edit
figures/          your figure exports — dummies + a QR placeholder shipped
logos/            KERMIT and Ghent University marks, ready to use
demo/             four worked posters, from minimal to kitchen sink

Before printing, set paper: "a4" and print that: a proportionally identical proof you can hold, which is a better check than any on-screen preview.

The demos

The four demo posters

File What it shows
demo/minimal-poster.typ Start here. Two columns, quiet palette, no frames, conclusion first.
demo/figures-first-poster.typ A hero figure, four short claims, almost no prose.
demo/boxed-poster.typ Every panel framed, bleeding key message, references from a .bib.
demo/demo-poster.typ The kitchen sink — every component at once. A test, not a design to copy.

All five posters share one body of machinery, byte-identical between them; only the config block and the content section differ. See Four temperaments. Build them all with:

for f in demo/*.typ; do typst compile "$f"; done

The demos keep their own copies of the logos in demo/assets/, because Typst resolves relative paths against the directory of the file it is compiling and demo/*.typ cannot reach ../logos/ without a --root flag. poster.typ at the root has no such problem.

Logos and figures

logos/ holds kermit-warm.svg (the default), kermit-cold.svg (quieter, for saturated bands), ugent.png and ugent-white.png (knockout, for a dark brand). The UGent files are cropped from the official exports to their ink plus 8 % clear space — the originals carry ~45 % transparent padding, which made a height: cap produce a logo half the intended size.

Logos appear once, in the footer. Repeating them in the header spends the second-most valuable space on the page telling the reader something they already know. The header's right-hand slot is art: instead — a teaser figure, a graphical abstract, or a portrait of whoever is standing next to the poster, which is the one thing that genuinely helps a passer-by find you:

art: "figures/header-art.svg",   // or "assets/portrait.svg", or none, or content

Both slots take a path or (src: .., scale: ..), because two institutional marks almost never carry the same optical weight at one height:

footer-logos: (
  "logos/kermit-warm.svg",
  (src: "logos/ugent.png", scale: 1.2),
),

art: is capped at 130 mm, which fills the band without forcing it taller or pushing a two-line title onto a third line.

figures/ ships dummies at 16:9, 5:2, 4:3 and 1:1 (the header slot) plus a placeholder QR, so a fresh fork compiles with figures in place. See figures/README.md for the export settings that survive poster magnification.

Two things to read before you start

Changing columns changes how much fits. At two columns the measure roughly doubles, so two columns wants ~34 pt body. Larger type and wider figures both cost height, so a two-column poster carries materially less content than a three-column one. The skeleton fits on one page at 2, 3 and 4 columns, but the headroom is ~7 mm at 2, ~80 mm at 3 and ~225 mm at 4. A figure keeps its aspect ratio, so its height scales with its column width: prefer wide figures, since a square one costs 230 mm of page at three columns and 360 mm at two.

Type has a floor and spacing does not. Everything a reader is expected to read clears 24 pt. scale-factor shrinks spacing only — see below.

Legibility floor

A0 posters are read from about 1.5 m. legibility-floor (default 24pt, page-scaled) is the smallest type on the poster, and it is enforced rather than documented:

Step pt at scale-factor: 1.0 at 0.7
title 130 91
section heading 46 32.2
body 30 24 — held
caption, chip 24 24 — held
references, funding, code 24 24 — held

scale-factor multiplies every spacing value and clamps every type size at the floor. At 0.7 the block gap drops from 24 mm to 16.8 mm and no step goes below 24 pt. That is deliberate: recommending a lower scale-factor to make a poster fit would otherwise trade legibility for fit without saying so. Set legibility-floor: 0pt to opt out — for a poster meant to be read at arm's length, say.

Two things the floor does not and cannot cover. Superscripts and subscripts — affiliation markers, the exponent in $10^4$ — render smaller than their surrounding text by typographic convention; they are single characters, not prose. And type inside an imported figure is the figure's business: export at sizes that survive the magnification (see Adding a figure).

How the page is built

The page has zero margin; each band applies the 45 mm inset itself. That is what lets the header and footer bleed to the trim with no white hairline. The body is a vertical stack:

#header-band(cfg)
#band[ ... ]              // cfg.columns columns
#keymessage[ ... ]        // full width, works at top level or nested
#band(cols: 2)[ ... ]
#v(1fr)                   // pushes the footer to the bottom edge
#footer-band(cfg)

A band takes the block-level items in its body, measures each one, and packs them into columns so the tallest column is as short as possible. Order is preserved: items fill column 1, then 2, then 3. Consequences worth knowing:

  • Typst's own columns() is not used. It only balances inside a container of known height, and a band's height is unknown until its content is laid out — which is why a naive columns() band dumps everything into column one.
  • A band never splits across pages, and no block is ever clipped.
  • A child of a band is treated as a block-level packing unit unless it is a known inline element. Custom components are safe, including ones that return a bare context element — which is what any component that calls measure() returns.
  • footer-band emits its own non-collapsible top spacer, because the v(1fr) above it would otherwise absorb the automatic block spacing and let the last block come to rest flush against the footer on a poster that exactly fills the page.

Components

Call Notes
panel(title: none, frame: none, body) The generic unit. frame is none / "rule" / "box".
keymessage(body) The takeaway. Once or twice per poster, never more.
fig(src, caption:, width:, ratio:, label:) Unnumbered caption below. src: none → placeholder.
callout(kind: "note", body) Filled box; kind: "definition" uses accent.
chip(body) Inline pill for a tag or short label.
codeblk(lang: none, highlight: false, body) Mono, tight leading, faint fill. Highlighting off — see Colour.
tbl(..) No vertical rules, one rule under the header row. Wrap header cells in thead[..][..].
refs(items) Compact numbered list from a hand-written array.
bibrefs(path, style:, full:) The same, from a .bib file. full: false prints only @key-cited entries.
funding(body) One-line acknowledgement.
sec(title) The section heading on its own. = Heading markup maps to it.
free(x:, y:, width:, body) Overlay layer — see below.
hand(body) The decorative font slot. A tagline or one annotation, never body text.

Every block-level component is legible with its frame off. Nothing depends on a border to define its extent.

Adding a figure

Three dummies are shipped, so a fresh fork compiles with figures in place. Replace a file keeping its name and nothing in the config changes.

To lay a poster out before an export exists, pass src: none and the box is drawn without the image:

#fig(none, ratio: 16/9, label: "figures/fig-method.svg",
     caption: [What the reader should take from it.])

The dashed box reports the path it is waiting for and its own rendered size in mm, so you know what to export. When the export exists, pass it as the first argument and delete label.

Set ratio to the aspect of the export you intend to produce, so nothing moves when it lands. Export with large type: a 6.4 in wide figure scaled into a 230 mm column is magnified 1.4×, so 17 pt in the export arrives at ~24 pt on the poster. demo/assets/make-figures.py shows the matplotlib settings; the same reasoning applies to Makie.

Do not let hue be the only channel. A reader with a red–green deficiency (~8 % of men), a greyscale printout, or a photocopy all collapse a colour-only encoding. The demo figures carry a second channel: the bars are hatched, the two series have distinct markers and dash patterns, and the class rows are labelled in words. This is the one accessibility problem the template cannot check for you.

Paper size and the fast proof

Every length is written at A0 and multiplied by the page width relative to A0, so paper: changes the size and nothing else:

paper: Size Compile
"a0" 841 × 1189 mm 0.15 s
"a1" 594 × 841 mm 0.07 s
"a2" 420 × 594 mm 0.07 s
"a4" 210 × 297 mm 0.07 s — the proof
"us-36x48" 914 × 1219 mm 0.08 s — aspect differs from ISO

paper: "a4" gives a proportionally identical poster you can print and hold. It is a better check than squinting at a PNG, and 15 % of A0 is roughly what you see from 1.5 m if you would rather do that:

typst compile poster.typ preview.png --ppi 24

Draft mode

draft: true adds a page-foreground overlay: hairlines every 0.1 of the page in both axes with their fraction values labelled, the column bands tinted, the margin box dashed, a DRAFT marker so a proof never reaches a printer, and a palette check in the bottom-left corner listing each role's luminance and its WCAG contrast ratio against ink, flagging anything under 4.5:1 and reporting the brand↔accent luminance gap. draft: false removes all of it.

Separately, placeholder-figs: true stubs out every figure at once, for laying a poster out before any export exists.

Independent of both, a poster that spills onto a second page gets an unmissable red OVERFLOW banner. That is always on: silently becoming two pages is the failure mode that survives a quick glance.

The overlay layer

For the things the grid cannot express — an arrow across two columns, a floating annotation, a logo in a corner:

#free(x: 0.105, y: 0.7775, width: 0.16)[#hand[flat from here on →]]

x, y and width are fractions of the full page, bleed to bleed; (0, 0) is the top-left page corner and y sets the block's top edge. Turn on draft and read the coordinates off the grid. Overlay content draws on top of band content.

Overlay items are drawn on page 1 only. If a poster overflows, page 2 gets the OVERFLOW banner and no overlay — repeating the annotations there would disguise the overflow rather than expose it.

Config knobs

Everything below the END CONFIG fence reads from cfg and is not edited per poster.

Knob Default Effect
title, subtitle — Title is 130 pt; keep it under ~42 characters or it takes a third line.
authors — (name:, affil: (1, 2), presenter: true). Presenter is bolded; superscripts are omitted when there is one affiliation.
affiliations — One per line, muted.
affiliations-in-footer false Moves them off the header. Worth ~12 mm per line of the most expensive space on the page — measured at 37 mm on boxed.
bib none Path to a .bib file, for bibrefs().
art a dummy The header's right-hand slot: a teaser figure, graphical abstract or portrait. A path, (src:, scale:), or content. none lets the title run the full width.
footer-logos KERMIT + UGent The only logo slot. Each entry is a path or (src:, scale:); capped at a max height so a wide logo cannot blow up the band.
qr, qr-caption figures/qr.png QR arrives as an image file; there is no in-Typst QR generation. The shipped one is not scannable.
funding — One line, in the footer.
paper "a0" See Paper size.
columns 3 Default column count for band(). A content-budget knob — see the top.
scale-factor 1.0 Spacing density. Type is clamped at the legibility floor.
legibility-floor 24pt Smallest type on the poster. 0pt to opt out.
draft false Grid overlay, DRAFT marker, palette check.
placeholder-figs false Force every fig to a placeholder.
heading-mark "none" "none" / "bar" / "tick" / "text" / "full".
heading-mark-role "support" Which role colours the mark, when there is one.
frame-role "accent" Which role strokes frame: "box" and frame: "rule".
keymessage-bleed false Whether a top-level key message runs to the trim.
flavour "frappe" latte / frappe / macchiato / mocha.
roles (:) Overrides, e.g. (accent: rgb("#c04a3f"), support: "mauve"). A string is looked up as a hue name in the active flavour.
fonts see file Fallback chains for sans, mono, hand.

Colour

Roles, never hues, in component code. Four rules:

  1. One hue, one role. A colour that means "methods" here does not also mean "emphasis".
  2. At most three accents in play: brand + accent + one of support / note.
  3. Accents are fills, never text colour. Coloured body text is prohibited. The one sanctioned exception is a figure-pointer label echoing the colour of the thing it points at.
  4. The header band and the key-message fill must differ in luminance by about 0.12. They are the two largest colour areas on the page; matched in lightness the poster reads flat, and it collapses entirely in greyscale. Draft mode reports this gap. Role fills that a reader never has to tell apart — note versus brand, in different structural positions — do not need separating; what genuinely does is any figure encoding a category by hue alone.

A caution on rule 4 if you tighten it: Catppuccin's accents are deliberately clustered in a narrow lightness band, which is what makes them pastel. Of the 364 three-hue combinations in Frappé, only 17 have all three pairs at least 0.12 apart, and every one of those includes rosewater. Demanding mutual separation across three roles effectively picks your palette for you — which is why the rule is scoped to the one pair that carries the page.

bg, ink, muted and panel are deliberately not Catppuccin values — the poster is dark ink on warm paper whichever flavour is active.

Code blocks are not syntax highlighted. Typst's highlighter sets an explicit fill on every span, which both imports hues from outside the role map and breaks rule 2 — and a show rule cannot undo it, so codeblk drops the language instead. Pass highlight: true to opt back in.

Four temperaments

Nothing but the config block and the content section changes between these.

minimal figures-first boxed demo
columns 2 throughout 1 · 3 · 4 3, plus two 2-col bands 3, plus a 2-col band
ordering takeaway first, then problem, figures, upshot hero figure, then takeaway, then detail question → method → takeaway → evidence question → method → takeaway → evidence
frames none none every panel one boxed panel
key message inset, under the header inset, below the hero bleeds to the trim inset, mid-page
heading mark none "tick" in support "bar" in brand none
palette five muted greys sky / blue / peach mauve / flamingo / yellow Frappé defaults
references refs() array none bibrefs() from .bib refs() array
affiliations header header footer header
scale-factor 1.0 1.0 0.94 0.98
overlay none two hand-lettered notes none one hand-lettered note

Two techniques worth stealing:

  • #v(1fr) between bands, not just before the footer. One v(1fr) pools all the leftover height above the footer, which reads as a mistake. Several share it out between the bands, which reads as air. minimal and figures-first do this.
  • Reordering is one moved call. keymessage adapts to its container, so conclusion-first is a matter of putting that block above the first band.

Decisions worth recording

  • Header ≈ 235 mm, footer ≈ 105 mm (auto height, tuned against the render). 235 mm is 20 % of the page, and a three-line title pushes it to ~295 mm, which is a quarter of the sheet. The 130 pt title earns its space; the affiliations underneath it mostly do not, which is what affiliations-in-footer is for — it brought boxed down to 198 mm. The footer's height is pinned by a fixed grid row so it stays a band whatever the slots contain.
  • Section headings carry no rule by default. A coloured line under every heading spends the accent colour dozens of times before the reader reaches the key message, which is the one place it should land. Bold weight at 46 pt already separates a heading from 30 pt body at A0, which is how the reference posters do it. Four marked alternatives remain, defaulting to support rather than accent for the same reason. A wrapping heading with "text" gets a full-width rule automatically, since the text already reaches the edge.
  • The key message respects the margin. With a 10 mm corner radius it reads as a card, and cards should not bleed. keymessage-bleed: true if you disagree. Its inset adapts to its container, so the same call works at the top of the document, inside a full-width band, and nested in one column.
  • Radii and insets: 8 mm on boxed panels and callouts, 10 mm on the key message, 18 mm box inset, 30 mm key-message inset (14 mm nested).

Things to know

  • Missing files cannot be detected. Typst has no way to test whether a path exists — image() on a missing path is a hard compile error with no way to catch it. Placeholders are requested explicitly instead: src: none for one figure, placeholder-figs: true for all of them.
  • Two to four font warnings on every compile are expected. They name the fallback families not installed on this machine (inter, libertinus sans, and jetbrains mono / fira code once a codeblk is used). The first available family in each chain wins; here that resolves to Helvetica Neue for text, DejaVu Sans Mono for code and Excalifont for hand. Trim the chains to silence them, at the cost of portability.
  • bibliography() may appear once per document, so bibrefs can be called once. full: false needs at least one @key citation elsewhere on the poster.
  • The logos are real; everything else in demo/assets/ and figures/ is a stand-in. The KERMIT and UGent marks are the actual files. The QR has correct QR geometry with random data modules and is not scannable; the figures, the .bib and all the demo content are invented. Replace them.
  • No licence file is included — that is your call. The poster machinery is yours to relicense; the institutional logos in logos/ are trademarks and are not covered by whatever licence you put on the code.

About

A typst template for posters - Michiel style

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages