Slidedown is a text-first presentation compiler. You write a plain text
.sd file with simple .directive{content} markup, and slidedown compiles it
to a browser-based HTML presentation.
It is meant for people who want slide decks that behave more like source code: diffable, scriptable, easy to version, and still capable of presentation effects such as progressive reveals, typewriter text, code highlighting, themes, and custom CSS.
View the README as an interactive presentation — pick a theme:
| Theme | Live Demo |
|---|---|
command-line |
https://fnndsc.github.io/slidedown/themes/command-line/ |
conventional-light |
https://fnndsc.github.io/slidedown/readme-presentation/ |
default |
https://fnndsc.github.io/slidedown/themes/default/ |
lcars-lower-decks |
https://fnndsc.github.io/slidedown/themes/lcars-lower-decks/ |
retro-terminal |
https://fnndsc.github.io/slidedown/themes/retro-terminal/ |
terminal |
https://fnndsc.github.io/slidedown/themes/terminal/ |
Slidedown is primarily managed through its Makefile. That path assumes your
system has make available.
On Linux and macOS, make is usually available through the system package
manager or developer tools. On Windows, use WSL, MSYS2, Git Bash, or another
environment that provides GNU Make. The Makefile also detects Android/Termux
and uses pip there instead of uv pip.
From a fresh checkout:
make devCompile and serve an example deck:
make presentation SOURCE=examples/minimal/minimal.sdThe first command creates .venv and installs slidedown in editable mode. The
second command compiles the deck and serves it at:
http://localhost:8000
Compile without serving:
make compile SOURCE=examples/minimal/minimal.sdServe an already compiled deck:
make serve SOURCE=examples/minimal/minimal.sdChange the theme or port:
make presentation \
SOURCE=examples/watermarked/light-watermarks-demo.sd \
THEME=conventional-light \
PORT=9000See all Makefile targets:
make helpIf you are on a system without make, the equivalent manual path is:
python3 -m venv .venv
.venv/bin/pip install -e ".[dev]"
.venv/bin/slidedown examples/minimal/ output/ \
--inputFile minimal.sd \
--theme conventional-light
cd output/
python3 -m http.server 8000.slide{
.title{Hello Slidedown}
.body{
.typewriter{> Welcome...}
.bf{Why use it?}
.o{Text-first authoring}
.o{Progressive reveal bullets}
.o{Browser-native presentation output}
}
}
Slidedown content is built from directives:
.directive{content}
.directive{.modifier{value} content}
.directive{nested .directives{work} too}
Common structural directives:
.slide{}defines one slide.title{}defines the slide title.body{}defines the visible slide content
Common formatting and behavior directives:
.bf{}bold text.em{}italic text.tt{}monospace text.code{}highlighted code block.typewriter{}typing animation.o{}progressive reveal bullet/snippet.nexus{}/.jump{}hub-and-spoke menus (see Nexus Decks).bars{}bar charts from data (see Bar Charts).term{},.takeaway{},.source{}terminal window, key line, source line
A nexus is a menu slide. Each entry jumps to a named slide, and the run of slides it lands on is a spoke. The presenter picks topics live, in any order, and comes back to the menu after each one. Use it for talks where the audience chooses what to hear, or where time decides how much you cover.
Give each spoke's first slide a name with .id{}, then list jumps to those
names inside .nexus{}:
.meta{
transition: zoom
}
.slide{
.title{Outline}
.body{
.nexus{.id{outline}
.div{.jump{.target{state} Current State of AI in Hospitals}}
.div{.jump{.target{governance} Governance}}
.div{.jump{.target{conclusions} Conclusions}}
}
}
}
.slide{.id{state}
.title{Current State}
.body{
.o{First point of the spoke.}
}
}
.slide{
.title{Current State, Continued}
.body{
.o{Still in the same spoke. Advancing past here returns to the outline.}
}
}
.slide{.id{governance}
.title{Governance}
.body{
.o{A one-slide spoke.}
}
}
.slide{.id{conclusions}
.title{Conclusions}
.body{
.o{Last spoke.}
}
}
Up/Down moves a cursor over the entries; Enter, a number key or a click opens
one. With transition: zoom, the chosen entry zooms into its slide.
How it behaves:
- Choose: press the entry's number (
1-9), move the cursor with Up/Down and press Enter, or click it. - Return: advancing off the end of a spoke returns to the menu that sent
you there.
BackspaceorEscreturns from anywhere inside a spoke. - Visited entries dim, so you can see what you've covered. Visited state is kept per browser tab; open a new tab to reset it after a practice run.
- Fullscreen: browsers keep
Escfor leaving fullscreen, so useBackspaceto return.ftoggles the deck's own fullscreen and, in desktop Chromium, keepsEscon the page too. - Reveal: wrap entries in
.o{}instead of.div{}to reveal them one at a time. Show the same menu again elsewhere with.nexus{.ref{outline}}. - Spoke extent is implicit: a spoke runs until the next slide that a jump points at, or the next nexus.
- Jumps also work inline as cross-references:
.jump{.target{state} see above}.
An outline in the command-line theme after sections 2 and 5 were visited.
From Computing Infrastructure for Clinical AI
(source).
A runnable demo is in examples/nexus/nexus-demo.sd;
the full design, including nested spokes, is in
docs/nexus-navigation.adoc.
Write a chart as data; slidedown draws it, and each row reveals in turn with its bar growing from zero:
.bars{.unit{$B}
.caption{2025, $ billions}
.bar{OpenAI earned .value{13.1}}
.bar{.color{red} OpenAI lost .value{20.9}}
}
Inside .bar{}, anything in a modifier is data and the bare text is the
label:
.value{13.1}-- the number that sets the bar's length.show{$13.1B}-- the text printed at the end of the bar (default: the value plus the chart's.unit{}; use it when they differ, as in.value{540} .show{9 min}).color{red}-- a theme palette name (accent,red,magenta,yellow,blue,green,orange,muted) or any CSS colour
On .bars{} itself: .unit{...}, .max{N} to fix the scale (otherwise the
largest value fills the width), .layout{horizontal|vertical}, and
.labelwidth{N} for the label column in characters (default 20; auto
fits the longest label).
.caption{...} adds a dim heading row and starts a new scale, so one chart
can hold groups in different units.
Grouped bars declare their series once with .key{} -- which also draws
the legend -- and give each bar one .value{} per key, in order:
.bars{.unit{%} .max{100}
.key{.color{accent} predictive}
.key{.color{magenta} generative}
.bar{all hospitals .value{71} .value{31.5}}
.bar{rural .value{56} .value{25.4}}
}
More examples, including vertical charts, in
examples/bars/bars-demo.sd. A directive name
written in prose must be escaped (\.unit\{\}); an unescaped chart part
outside a .bars{} fails the build.
Use --theme to select a theme:
slidedown input/ output/ --inputFile deck.sd --theme retro-terminalIncluded themes:
command-line(terminal windows around typed commands; see below)conventional-lightdefaultterminalretro-terminallcars-lower-decks
In command-line, .term{} is a terminal window: its typed command, then
its bullets, with the cursor following the last revealed line. A nexus
inside one prints line by line once the command finishes. .takeaway{} is
the slide's one line to remember, .source{} its source line.
.term{
.typewriter{$> whatido --at=bch}
.o{Build the infrastructure that runs research computation inside a hospital.}
}
.takeaway{THE HARD PART WAS NEVER THE MODEL.}
.source{FNNDSC, Boston Children's Hospital}
Window titles come from one template in .meta, filled in per slide from
where the slide sits in the deck, so they follow slides when they move:
.meta{
theme: "command-line"
label: "me@host: ~ {n} {spoke}"
}
{spoke} is the spoke the slide belongs to (its address, hyphens as spaces),
or the slide's own address outside any spoke; {n} is that spoke's number in
its menu (03); {slide} is the slide's own address. A slide can set its own
with .label{...}. Other themes can show the label too: it reaches the page
as the --sd-label CSS variable on each slide.
Use .meta{} for deck-wide settings:
.meta{
title: "Demo Deck"
typography:
baseline: presentation
snippets:
marker: "-> "
}
Typography baselines include compact, normal, large, xlarge, and
presentation. For per-slide tuning, add a density class:
.slide{.class{dense}
.title{Detailed Slide}
.body{
Dense slides fit more content while preserving presentation scale.
}
}
Supported density classes are hero, roomy, dense, and compact.
- Complete guide:
docs/sd-guide.adoc - Nexus navigation design:
docs/nexus-navigation.adoc - Tips and patterns:
docs/tips-n-tricks.adoc - LCARS theme guide:
docs/lcars.adoc - Type system reference:
docs/types.adoc - Examples:
examples/ - README presentation source:
docs/readme-presentation.sd
Run the checks:
make lint
make typecheck
make testUseful development targets:
make format
make clean
make purge
make readme-presentationProject conventions:
- Python code uses explicit type hints
- Python lines stay under 80 columns
- Public docstrings use Google-style
ArgsandReturnssections - Internal names prefer RPN-style
object_verbnaming where practical
Slidedown is released under the MIT License. See LICENSE.

