A monorepo for managing presentation decks and custom Slidev themes.
.
├── themes/ # Custom Slidev themes
│ ├── paiza/
│ ├── academic/
│ ├── dracula/
│ ├── frankfurt/
│ ├── vscode-dark/
│ ├── cobalt/
│ ├── emerald-synth/
│ └── barrel/
├── decks/ # Presentation decks
├── docs/
│ └── themes.md # Theme concepts and layout policy
├── .docker/
│ └── traefik/ # Reverse proxy config
├── Dockerfile
├── compose.yaml # Main compose (traefik, dev, includes)
├── compose.themes.yaml # Theme services
└── compose.decks.yaml # Deck services
mkcert -install && mkdir -p .docker/traefik/certs && mkcert -cert-file .docker/traefik/certs/_wildcard.slides.localhost.pem -key-file .docker/traefik/certs/_wildcard.slides.localhost-key.pem "*.slides.localhost"docker compose up -d# Theme preview
docker compose --profile paiza-theme up -d
# Deck
docker compose --profile lt-devin up -ddocker compose down --remove-orphansAccess via HTTPS:
| Profile | URL |
|---|---|
| paiza-theme | https://paiza-theme.slides.localhost |
| academic-theme | https://academic-theme.slides.localhost |
| dracula-theme | https://dracula-theme.slides.localhost |
| frankfurt-theme | https://frankfurt-theme.slides.localhost |
| vscode-dark-theme | https://vscode-dark-theme.slides.localhost |
| cobalt-theme | https://cobalt-theme.slides.localhost |
| emerald-synth-theme | https://emerald-synth-theme.slides.localhost |
| barrel-theme | https://barrel-theme.slides.localhost |
| lt-devin | https://lt-devin.slides.localhost |
| self-intro | https://self-intro.slides.localhost |
| (dashboard) | https://traefik.slides.localhost |
docker compose --profile dev up -d
docker compose exec dev shdocker compose --profile export run --rm export decks/202603-self-introThe argument is any workspace directory with an export script. The PDF is written
next to the source (e.g. decks/202603-self-intro/slides-export.pdf).
Chromium is not part of the regular images. The export service uses a dedicated
build stage carrying only the system libraries Chromium needs, and downloads the
browser itself on first use into the playwright_browsers volume (~1 GB). Later
runs reuse it. To reclaim the space:
docker compose down --remove-orphans && docker volume rm slides_playwright_browsers| Theme | Description |
|---|---|
| paiza | Corporate theme for paiza presentations |
| academic | Academic presentation theme |
| dracula | Dracula color scheme theme |
| frankfurt | Frankfurt-style academic theme |
| vscode-dark | VS Code inspired dark theme |
| cobalt | Deep cobalt blue framed layouts |
| emerald-synth | Synthwave/cyberpunk dark theme with neon glow |
| barrel | Warm amber dark theme with refined typography |
Each theme is built around a different idea, and that decides which layouts belong in it. docs/themes.md records those concepts and the rule for spreading a layout from one theme to another.
- Create a directory under
decks/ - Add a
package.jsonwith@slidev/clias a dev dependency - Reference the theme via relative path in frontmatter:
---
theme: ../../themes/paiza
---- Add a service block to
compose.decks.yamlwith a profile and Traefik labels
-
Decide which of the three types it is — Chrome, Palette or Page. The type decides which layouts belong in it; see docs/themes.md.
-
Create a directory under
themes/. Thethemes/*workspace glob picks it up, sopnpm-workspace.yamlneeds no change. -
Add a
package.json. Copy one from an existing theme and rename it; the fields that carry weight are:Field Why nameslidev-theme-<name>— the prefix Slidev resolves a bare theme name by once published. Locally,example.mdand the decks point at a path insteadkeywordsmust include slidev-themefileseach theme is meant to stand alone as a published package slidev.colorSchemadark,lightorbothslidev.defaults.fontsthe theme's default font stack scriptsbuild/dev/export/screenshot, all pointing atexample.md -
Add
styles/index.tsto pull in Slidev's base layout styles and your own:
import "@slidev/client/styles/layouts-base.css";
import "./base.css";- Add
layouts/,components/,setup/anduno.config.tsas the theme needs them. Layouts are optional — vscode-dark ships none and restyles the built-ins instead. Slidev resolves layouts in the order default → theme → addon → custom, so the built-ins are always available to fall back on. - Write an
example.mdthat renders every layout the theme ships. - Add a service block to
compose.themes.yamlwith a profile and Traefik labels, plus a row in the URL table above.
- Scope stylesheet rules with
.slidev-layout. Slidev'slayouts-base.cssstyles.slidev-layout h1and friends, so a bareh1loses on specificity and the tempting fix is!important— which the linter rejects. Matching the selector wins on source order instead. Custom properties are the exception: they inherit, so declaring them on.slidev-layoutalready beats Slidev'shtml.dark. - Every layout belongs in
example.md. A layout nothing renders is a layout nobody checks — that gap is how barrel shippedcardsandcomparewith no CSS at all. - A green build proves very little. A missing layout silently falls back to a built-in, a missing rule renders as an unstyled div, and the wrong prop shape renders blank. Open the theme in a browser before calling it done.
- emerald-synth and barrel are twins. They share a skeleton and differ only in
palette, and
pnpm check:themesholds them to sharing every class name.