Markdown-based slide creation tool for research talks. Git-friendly, AI-drivable, single-file HTML output.
Docs · Core Example (RLHF Book) · PyPI
Colloquium uses uv for fast, reliable Python package management.
uv tool install colloquium
# or inside a project
uv pip install colloquiumFor development:
git clone https://github.com/natolambert/colloquium.git
cd colloquium
uv pip install -e ".[dev]"Create a markdown file:
---
title: "My Talk"
author: "Jane Doe"
date: "2026-02-22"
---
# My Talk
Jane Doe
---
## Key Results
- Finding one
- Finding two
---
## Conclusion
Thanks for listening!Build it:
colloquium build slides.md # → slides.html
colloquium serve slides.md # dev server with live reload
colloquium export slides.md # PDF via a headless Chromium-based browser
colloquium capture slides.md # per-slide PNGs for AI review| Command | Description |
|---|---|
colloquium build <file.md> |
Build to self-contained HTML |
colloquium serve <file.md> |
Dev server with live reload |
colloquium export <file.md> |
PDF export (requires a Chromium-based browser) |
colloquium capture <file.md> |
Capture slides as individual PNGs |
All configuration goes in the YAML frontmatter block at the top of the file.
---
title: "Talk Title"
author: "Author Name"
date: "2026-02-22"
theme: default
aspect_ratio: "16:9"
fonts:
heading: "Playfair Display" # Google Font for h1/h2/h3
body: "Source Sans 3" # Google Font for body text
footer:
left: "https://example.com/logo.png" # image URL → logo, plain text → text
center: "My Talk Title"
right: "auto" # "auto" → slide numbers "3 / 12"
custom_css: ".slide h2 { color: red; }" # inline CSS overrides
---| Key | Default | Description |
|---|---|---|
title |
"Untitled" |
Presentation title (used in <title> and title slides) |
author |
"" |
Author name |
date |
"" |
Date string |
theme |
"default" |
Theme name |
aspect_ratio |
"16:9" |
Slide aspect ratio |
fonts.heading |
Inter | Google Font for headings |
fonts.body |
Inter | Google Font for body text |
footer.left |
"" |
Left footer zone (text, image URL, or "auto") |
footer.center |
"" |
Center footer zone |
footer.right |
"auto" |
Right footer zone ("auto" = slide numbers) |
custom_css |
"" |
Additional CSS injected into the page |
Footer text supports {n} (slide number) and {N} (total slides) for custom counters, e.g. "Lambert {n}/{N}" → "Lambert 6/23".
When footer: is omitted entirely, a minimal footer with just the slide counter in the right zone is used.
Use {n} and {N} placeholders to embed the current slide number and total count inline with text:
footer:
left: "rlhfbook.com"
right: "Lambert {n}/{N}" # renders as "Lambert 3/25"If no zone uses "auto", {n}, or {N}, the slide counter is automatically placed in the first empty zone.
Slides are separated by --- on its own line. The first heading in each slide becomes the slide title:
| Heading | Behavior |
|---|---|
# Title |
Title slide — centered, large text, slide--title layout |
## Title |
Content slide — standard layout with heading at top |
### through ###### |
Rendered as subheadings within slide content |
# Welcome ← title slide (centered, large)
---
## Key Results ← content slide (heading + body)
### Sub-section ← rendered as h3 inside the slide body
Normal paragraph text.Per-slide configuration via HTML comments. Place them anywhere in the slide.
## My Slide
<!-- layout: section-break -->
<!-- class: highlight special -->
<!-- style: background: #1a1a2e -->
<!-- notes: Remember to mention X -->
<!-- align: center -->
<!-- valign: bottom -->
<!-- columns: 2 -->
<!-- rows: 35/65 -->
<!-- padding: compact -->
<!-- size: large -->
<!-- title: hidden -->| Directive | Effect |
|---|---|
<!-- layout: title --> |
Centered title slide (used with # Heading) |
<!-- layout: title-left --> |
Left-aligned title slide with stacked metadata |
<!-- layout: title-sidebar --> |
Wide title with a right-side metadata rail |
<!-- layout: title-banner --> |
Editorial title slide with headline up top and metadata near the bottom |
<!-- layout: content --> |
Default content layout (used with ## Heading) |
<!-- layout: section-break --> |
Dark accent background, centered text |
<!-- layout: two-column --> |
Two-column grid |
<!-- layout: image-left --> |
Image on left, text on right |
<!-- layout: image-right --> |
Text on left, image on right |
<!-- layout: code --> |
Optimized for large code blocks |
Split content with ||| between columns:
<!-- columns: 2 -->
## Results
Left column content
|||
Right column content| Value | Effect |
|---|---|
2 |
Two equal columns |
3 |
Three equal columns |
60/40 |
60%/40% split |
40/60 |
40%/60% split |
70/30 |
70%/30% split |
30/70 |
30%/70% split |
Arbitrary numeric ratios are allowed, e.g. 25/75, 33/67, or 1/2/1.
Split a slide vertically with === between row blocks:
<!-- rows: 35/65 -->
## Overview
Top row content
===
Bottom row content| Value | Effect |
|---|---|
2 |
Two equal rows |
3 |
Three equal rows |
60/40 |
60%/40% split |
40/60 |
40%/60% split |
35/65 |
35%/65% split |
65/35 |
65%/35% split |
70/30 |
70%/30% split |
30/70 |
30%/70% split |
Arbitrary numeric ratios are allowed here too, e.g. 25/75 or 20/30/50.
To use columns inside a specific row block, add a row-local directive at the top of that row:
<!-- rows: 35/65 -->
## Overview
<!-- row-columns: 40/60 -->
Left text
|||
Right text
===
At the slide root, use either columns: or rows:. For nested layouts, use row-columns: inside a row block.
| Directive | Values |
|---|---|
<!-- align: ... --> |
left, center, right |
<!-- valign: ... --> |
top, center, bottom |
<!-- size: ... --> |
small (20px), normal (24px), large (28px) |
<!-- padding: ... --> |
compact (30px), normal (60px), wide (90px) |
<!-- title: ... --> |
top, center, hidden |
| Directive | Description |
|---|---|
<!-- class: name1 name2 --> |
Add CSS classes to the slide |
<!-- style: css-here --> |
Inline CSS on the slide element |
<!-- notes: text --> |
Speaker notes (hidden in presentation) |
<!-- class: figure-captions --> |
Turn standalone markdown images on that slide into numbered figures with captions taken from  |
<!-- class: no-figure-captions --> |
Disable deck-wide figure captions for a specific slide |
<!-- img-align: center --> |
Align images only (left, center, right) — title unaffected |
<!-- img-valign: top --> |
Vertically align standalone images in grid/row cells (top, center, bottom) |
<!-- img-fill: true --> |
Expand image to fill available slide space |
<!-- img-overflow: true --> |
Let images in grid cells bleed outside their box instead of fitting inside |
See the examples on the website for rendered decks and copy-paste patterns, or browse the source in examples/.
Frontmatter options:
bibliography: refs.bib
citation_style: author-year
citation_order: autocitation_style: numerickeeps citations and references in first-appearance order.- Non-numeric styles default to alphabetical ordering.
- Set
citation_order: appearanceto keep author-year or title-year citations in source order.
Math (KaTeX) — inline $E=mc^2$ and display $$\sum_{i=1}^n x_i$$
Code (highlight.js) — fenced code blocks with language syntax highlighting
Tables — standard markdown tables
Images —  with automatic sizing (SVG supported for vector graphics)
To enable numbered figure captions across a whole deck, add this to frontmatter:
figure_captions: trueStandalone markdown images will render as numbered figures, using the alt text as the caption:
This renders as Figure 1: Training recipe overview. Leave the alt text empty to suppress the caption:
If you only want captions on selected slides, use figure-captions as a slide class instead:
<!-- class: figure-captions -->
If the deck enables figure_captions: true, add no-figure-captions to a slide to opt that slide out.
Control font size on any element or block using HTML class attributes:
<span class="text-2xl">Big emphasis text</span>
Normal paragraph text.
<div class="text-sm">
- Dense bullet point one
- Dense bullet point two
</div>
<span class="text-xs">Footnote or citation</span>
<div class="colloquium-spacer-md"></div>
<div class="colloquium-footnote">
This is useful for speaker-side caveats or details that should stay visually secondary.
</div>| Class | Scale | Use case |
|---|---|---|
text-xs |
0.65em | Footnotes, citations |
text-sm |
0.8em | Dense lists, fine details |
text-base |
1em | Default |
text-lg |
1.2em | Callouts |
text-xl |
1.4em | Key takeaways |
text-2xl |
1.7em | Emphasis |
text-3xl |
2.2em | Large statements |
text-4xl |
2.8em | Hero text |
Use small spacer blocks when you want more breathing room between prose and an example:
After pretraining we are left with a glorified autocomplete model.
<div class="colloquium-spacer-md"></div>
```conversation
messages:
...
Available spacer helpers:
- `colloquium-spacer-sm`
- `colloquium-spacer-md`
- `colloquium-spacer-lg`
Use `colloquium-footnote` for secondary caveats or context inside the slide body:
```markdown
<div class="colloquium-footnote">
Base models are also becoming more flexible through midtraining and related data mixtures.
</div>
For slide-level footnotes that sit above the footer, use directives:
<!-- footnote: Base models are also becoming more flexible through midtraining. -->
<!-- footnote-right: Right-aligned note for this slide. -->These share the same bottom-left / bottom-right area as floating citations, with citations stacked above the footnote when both are present.
For inline numbered footnotes, use ^[...] where you want the marker to appear:
This sentence ends with a numbered footnote.^[Base models are also becoming more flexible through midtraining.]Inline footnotes collect into the same floating footer area and are numbered per slide. They default to the right side; set the side explicitly when needed:
<!-- footnotes: left -->Use the dedicated example deck in examples/footnotes/ for copy-paste patterns.
Inline charts via Chart.js using YAML in fenced code blocks:
```chart
type: line
height: 500
width: 800
data:
labels: [Q1, Q2, Q3, Q4]
datasets:
- label: Revenue
data: [10, 25, 40, 60]
color: "#4AA691"
options:
scales:
y:
ticks:
prefix: "$"
suffix: "K"
```| Key | Default | Description |
|---|---|---|
type |
bar |
Chart type: line, bar, scatter, pie, doughnut |
height |
400 |
Container height in pixels |
width |
100% |
Container width in pixels (omit for full width) |
data.labels |
[] |
X-axis labels |
data.datasets[].label |
"Series N" |
Legend label |
data.datasets[].data |
[] |
Data values |
data.datasets[].color |
auto | Series color |
options.scales.{x,y}.ticks.prefix |
"" |
Prepend to tick labels (e.g. "$") |
options.scales.{x,y}.ticks.suffix |
"" |
Append to tick labels (e.g. "%") |
options.scales.{x,y}.grid.display |
true |
Show/hide grid lines |
Render LLM-style chat bubbles using YAML in fenced code blocks:
```conversation
size: 0.9
messages:
- role: user
content: "What is RLHF?"
- role: assistant
model: "Tülu 3 405B"
content: "**RLHF** is a technique for aligning language models..."
- role: system
content: "You are a helpful AI assistant."
```| Role | Styling |
|---|---|
user |
Right-aligned bubble, accent background, white text |
assistant |
Left-aligned bubble, code-bg background |
system |
Centered, bordered, muted italic text |
Message content supports markdown formatting (bold, italic, inline code).
Optional conversation settings:
| Key | Default | Description |
|---|---|---|
size |
unset | Bubble font scale as a positive numeric value like 0.9 or 1.05 |
messages[].model |
unset | Optional model label shown next to the role, e.g. ASSISTANT (Llama 3.1 405B Base) |
Render a rounded callout card using YAML in fenced code blocks:
```box
title: DPO became popular because:
tone: accent
content: |
- Far simpler to implement
- Far cheaper to run
- Often reaches most of the final performance
```Optional box settings:
| Key | Default | Description |
|---|---|---|
title |
unset | Bold heading shown at the top of the box |
tone |
accent |
Visual style: accent, muted, or surface |
content |
unset | Optional markdown body below the title |
size |
unset | Font scale as a positive numeric value like 0.9 or 1.05 |
align |
unset | Text alignment inside the box: left, center, or right |
compact |
false |
Tighten paragraph and list spacing inside the box |
box supports a title-only callout too:
```box
title: Core idea
tone: accent
```The supported tones are:
| Tone | Use |
|---|---|
accent |
High-contrast highlight box using the deck accent color |
muted |
Softer supporting card using the code/background surface |
surface |
Neutral bordered panel for references, caveats, or side notes |
Render a compact GitHub badge anywhere in a slide using the normal layout tools:
<!-- align: right -->
<!-- valign: bottom -->
```builtwith
repo: natolambert/colloquium
```Optional settings:
| Key | Default | Description |
|---|---|---|
label |
"Built with" |
Small label above the repo name |
repo |
"natolambert/colloquium" |
GitHub repository slug |
url |
https://github.com/<repo> |
Link target override |
stars |
auto |
Fetch GitHub stars automatically, hide with false, or pin a numeric value |
icon |
true |
Show/hide the GitHub icon |
Because it is a normal block element, you can place it with columns, rows, alignment utilities, or raw HTML wrappers instead of relying on footer-specific behavior.
Add a .bib file to your project and reference it in frontmatter:
---
bibliography: refs.bib
citation_style: author-year # or "numeric" or "title-year"
---Use [@key] to cite in slides:
The foundational work on RLHF [@christiano2017] introduced reward models.
Multiple citations: [@christiano2017; @ouyang2022]A References slide is automatically appended with only the cited works.
| Key | Default | Description |
|---|---|---|
bibliography |
"" |
Path to .bib file (relative to markdown file) |
citation_style |
"author-year" |
Citation format: author-year, numeric, title-year |
| Key | Action |
|---|---|
| Right / Down / Space / PgDn | Next slide |
| Left / Up / PgUp | Previous slide |
| Home | First slide |
| End | Last slide |
| F | Toggle fullscreen |
| Escape | Close picker / exit fullscreen |
Click the slide counter to open the slide picker. Click left 1/3 of screen to go back, right 2/3 to go forward.
Export to PowerPoint/Google Slides format:
uv pip install colloquium[pptx] # install optional dependency
colloquium export --pptx slides.md # → slides.pptxThis produces a reasonable starting point, but some colloquium features lose fidelity: citations are flattened, math renders as raw LaTeX, and custom themes/CSS aren't applied. Charts and tables become native editable PPTX objects.
Two options:
- Browser: Open the HTML file and
Cmd+P/Ctrl+P— print CSS makes all slides visible with page breaks, footers with slide numbers included - CLI:
colloquium export slides.mduses a headless Chromium-based browser
Capture individual slides as 1280x720 PNG images — useful for AI agents to visually review slides, or for thumbnails and previews.
colloquium capture slides.md # all slides → slides/ directory
colloquium capture slides.md -o ./imgs/ # custom output directory
colloquium capture slides.md --slide 3 # capture only slide 3This exports to PDF first (one headless Chrome launch), then splits pages into PNGs with Ghostscript. Requires Chrome and gs:
brew install ghostscript # macOS
apt install ghostscript # LinuxEverything builds to a single self-contained HTML file. CSS and JS are inlined; math (KaTeX) and code highlighting (highlight.js) load from CDN.
Custom block-level elements live in colloquium/elements/. Each module exposes:
PATTERN— compiled regex matching<pre><code class="language-X">...</code></pre>process(yaml_str) -> str— converts the YAML content to HTMLreset()(optional) — resets any counters between builds
The registry in colloquium/elements/__init__.py auto-wires them into the build pipeline. To add a new element:
- Create
colloquium/elements/my_element.pywithPATTERN,process, and optionallyreset - Import and register it in
colloquium/elements/__init__.py - Add any element-specific CSS to
colloquium/themes/default/theme.css