A starting point for building presentation decks at Cogapp. Astro 7 + MDX, themed with the Cogapp palette and brand fonts (Civil + Untitled Serif).
Write each slide as an MDX file, set the order in one list, and present in the browser with keyboard navigation. Export the whole deck to a single PDF.
Live demo: https://cogapplabs.github.io/cogapp-slides-template/ (the example slides, deployed from this repo).
On GitHub, click Use this template to create a new repo from it, or:
gh repo create CogappLabs/my-deck --template CogappLabs/cogapp-slides-templateThen, in the new repo:
- Set
baseinastro.config.mjsto/<your-repo-name>(andsiteto your org Pages URL). Everything else, including the font URLs, followsbaseautomatically. - Edit
src/deck.config.ts: the deck title and subtitle, the credit line on the title slide, andslideOrder. - Replace the example slides in
src/content/slides/.
npm install
npm run dev # localhost:4321Index at /. Individual slides at /slide/<slug> where slug is the mdx
filename (e.g. /slide/bullets). Arrow keys / PageUp-Down navigate; Home/End
jump to first/last; F toggles fullscreen; P opens the presenter view;
? lists every shortcut.
P, or the monitor button in the slide nav, opens /presenter/<slug> in a
second window: speaker notes, a timer with pause and reset, the slide count,
and the next slide's title. Moving in either window moves the other.
Sync uses BroadcastChannel, which reaches other tabs in the same browser
only. Someone opening a presenter URL on another machine gets a standalone
view that doesn't follow along. Note that notes are published with the deck,
so anyone with the URL can read them.
npm run build # outputs to dist/
npm run preview # serve the built decknpm run build # build first
npm run pdf # renders dist/ to deck.pdf, one slide per pagescripts/build-pdf.ts runs the built deck through Playwright Chromium and
merges one landscape page per slide. It imports slideOrder and base from
the real sources, so there's nothing to keep in sync. Needs Playwright's
Chromium (npx playwright install chromium). Runs as TypeScript directly on
Node 22.18+ / 23+ via built-in type stripping.
src/deck.config.ts is the one file to edit:
| Export | What it sets |
|---|---|
deck |
Title, subtitle, credit line on the title slide, <title> suffix |
slideOrder |
The sequence, and which slides are in the deck at all |
surfaces |
Which colours a slide may use as its bg |
(src/content.config.ts is Astro's own file: the frontmatter schema. It reads
the background names from deck.config.ts.)
- Content lives in
src/content/slides/<slug>.mdx - Order is
slideOrderinsrc/deck.config.ts(move a line to reorder). A slide file not listed there is excluded with no warning; a slug listed with no file is a build error. - Frontmatter:
title, optionalbg,align,section,eyebrow,notes,docs
| Frontmatter | Purpose |
|---|---|
title |
Slide title (used in <title> and the index) |
bg |
Background theme (see below). Default cream |
align |
start (top) or center. Default start |
section |
Groups the slide in the index, and labels it above the content |
eyebrow |
Label above the content only, when it should differ from section |
notes |
Speaker notes. Shown in the presenter view, never on the slide |
docs |
URL, array of URLs, or {label, href} objects. Renders a collapsible References list |
The Cogapp palette: a cream base, slate ink, and four pastel accents for section and feature slides.
bg |
colour | text |
|---|---|---|
cream |
Cream #ebebe1 |
slate |
slate |
Slate #282828 |
cream |
pink |
Pink #ffdaea |
slate |
green |
Green #edffda |
slate |
purple |
Purple #e8daff |
slate |
blue |
Blue #dae9ff |
slate |
white |
White | slate |
Use cream, slate or white for content-heavy slides; the four pastels are
accents, best on section dividers and sparse feature slides where the lighter
contrast against slate text reads fine.
All colours are --color-* tokens in src/styles/global.css and resolve as
Tailwind utilities (bg-pink, text-slate, etc.). Two muted neutrals,
grey and light-grey, round out the palette for secondary text and panels.
Adding a background takes two steps: define --color-<name> in the @theme
block, then pair it with its text colour in surfaces in src/deck.config.ts.
That list is what makes the name valid in frontmatter, so the schema rejects a
bg you haven't defined. Each pairing carries body text across a whole slide,
so check it clears WCAG AA.
global.css holds the @theme tokens and imports the rest: fonts.css
(@font-face), transitions.css (slide animation), slide-content.css
(.prose-slide, the typography for MDX output), and layout-utilities.css.
Everything outside .prose-slide is Tailwind utilities.
Two of those layout classes exist because Tailwind has no equivalent:
stretchgives an element the height left on the slide, so a tall image or video shrinks to fit rather than pushing content off the bottomstacklayers children in one grid cell, centred on each other
Slides are markdown. Headings, lists, tables, quotes, code and links are styled
by .prose-slide, so a slide rarely needs any markup of its own:
ulgets dash markers,olkeeps numbers, nesting works- a
>blockquote is the pull quote; a final paragraph inside it is the attribution - the paragraph after the heading becomes the large lead line
- a trailing paragraph is set back as a note
- fenced code is syntax-highlighted by shiki and sized for the room. Tag a
block
```terminaland it renders as a terminal window instead
Alt-click anywhere on a slide (Ctrl-click on Linux) magnifies that spot 2× for the audience. Click again, or press Escape, to reset.
Components cover the things markdown has no syntax for. Import them through the
@/ alias, which points at src/:
import Video from "@/components/Video.astro";Eyebrow: the uppercase label rendered fromeyebrow/sectionfrontmatterTwoCol: two columns;ratio="2-1"or"1-2"to weight a sideByline: Cogapp logo with the credit line fromdeck.config.tsVideo: 16:9 YouTube embed; takes an id or any share URL, plus atitleReferences: collapsible list rendered from thedocsfrontmatterPoll: React island, a live show-of-hands tally. Needs aclient:*directive
The example slides demonstrate each one.
Cogapp brand faces. Cogapp holds redistribution rights for both:
- Civil (ABC Dinamo) →
--font-sans(body, labels, UI) - Untitled Serif (Klim) →
--font-serif(headings)
Files live in public/fonts/, declared as @font-face in fonts.css, and
exposed as Tailwind tokens so font-sans / font-serif resolve everywhere.
Reusing the fonts outside a Cogapp context is the reuser's responsibility.
Astro 7, MDX, Tailwind v4, Node 22.18+ (the PDF script runs as TypeScript directly, which needs that version's type stripping).
Markdown is rendered by Sätteri, Astro 7's own processor. It applies GFM, so tables and strikethrough work without a remark plugin.
React is available for interactive islands (client:load on a .tsx
component). The overrides block in package.json pulls @vitejs/plugin-react
up to v6, the Rolldown/Oxc build: @astrojs/react still asks for v5, which
prints deprecation warnings on every Vite 8 build. Drop the override once Astro
bumps its own dependency.
CLAUDE.md holds a short guide for working on the deck with Claude Code (the
authoring procedure, theme rules, and the things that are easy to get wrong).
.github/workflows/deploy.yml builds and publishes to GitHub Pages on push to
main. Enable Pages (source: GitHub Actions) in the repo settings.
noindex, nofollow on every page and public/robots.txt denies all crawlers.
Don't commit private client data.