Presenting with MD-Slides

The complete feature reference

How to read this tour

Every feature is demonstrated by using it. Two-column slides show the Markdown on the left and what it produces on the right.

Try it lines are things to do right now, on this deck. If one doesn't work, that's a bug: this deck is MD-Slides' tutorial and its acceptance test.

Slide separators (---) can't appear as bare lines inside code blocks, so the examples show slide bodies only. Open feature-tour.md for the full format.

Eight sections: Templates · Content · Speaker view · Navigation · Any screen · CLI · Themes · Validation

Templates

Six slide types — each with named slots and enforced constraints

title template

Three slots: H1 title (required, ≤ 2 lines), H2 subtitle (optional, ≤ 2 lines), **Author** (optional, ≤ 80 chars).

# Main Title
## Optional subtitle
**Optional author**

title is the only template that uses H1. All other templates use H2. The first slide of this deck uses the title template.

content template

The content template is the workhorse — most slides use it. Two slots:

## Slide Heading

Body: markdown, lists, code blocks, images, tables.

Heading: required, ≤ 80 chars. Body: ≤ 12 lines and ≤ 150 words. Validation reports all violations together — you fix everything in one pass, not one at a time.

section-title and closing templatesOpen chapters with section-title:

template: section-title

## Chapter Title

Subtitle text

Both parse like content. Themes assign each a distinct visual identity.

End the deck with closing:

template: closing

## Thank You

Questions welcome

Themes can apply full-bleed backgrounds to these templates automatically.

two-column template

Split a slide into two independent columns using the ---column--- delimiter:

template: two-column
## Heading
Left column content here.
---column---
Right column content here.

Each column: ≤ 10 lines, ≤ 75 words. The ---column--- delimiter must be on its own line. The next slide demonstrates a live two-column layout.

two-column in action: before and afterSequential — simple but slow:

def processAll(items: List[Item]): Unit =
  items.foreach(item => process(item))

Each item processed one at a time. Fine for small lists; bottleneck at scale.

Parallel — Cats Effect:

def processAll(items: List[Item]): IO[Unit] =
  items.parTraverse(item => process(item))

parTraverse runs all items concurrently using the IO thread pool.

diagram template

The diagram template renders Mermaid charts to SVG at build time — requires mmdc installed.

template: diagram
caption: Optional caption
## Slide heading
```mermaid
graph LR
    A[Input] --> B[Output]
```

Flowcharts, sequence, class, Gantt, pie, ER, and state diagrams are supported. The next slide is a live diagram slide.

Mermaid diagram: live render

Markdown file

Parser

Validator

Renderer

HTML output

The MD-Slides render pipeline

Frontmatter: complete key reference

Every slide opens with a frontmatter block between --- markers. Supported keys:

KeyRequiredDescription
template:yesSlide type: title content section-title two-column diagram closing
header:noTop bar text — tokens: {{pageNumber}} {{totalPages}} {{timer}} {{title}} {{date}}
footer:noBottom bar text — same tokens as header:
vertical-align:noContent position: top center (default) bottom
background:noPer-slide background image path (overrides theme for this slide only)
caption:noCaption below diagram (diagram template only)

Content

Formatting, lists, code blocks, images, and tables

Inline formatting: you write / you get

**Bold** text
*Italic* text
`Inline code`
[Link text](https://github.com/TJMSolns/MD-Slides)
~~Strikethrough~~

Bold text Italic text Inline code Link text Strikethrough

Lists: you write / you get

- First item
- Second item
  - Nested level 2
    - Nested level 3

1. Ordered first
2. Ordered second
   - Mixed nesting
  • First item
  • Second item
    • Nested level 2
      • Nested level 3
  1. Ordered first
  2. Ordered second
    • Mixed nesting

Code blocks: syntax highlighting

Fenced code blocks with a language name get full syntax highlighting:

case class Slide(id: SlideId, template: Template, slots: Map[SlotName, SlotContent])

object SlideDeck:
  def validated(slides: List[Slide]): Either[NonEmptyList[ValidationError], SlideDeck] =
    Either.cond(
      slides.nonEmpty,
      SlideDeck(slides),
      NonEmptyList.one(ValidationError("Empty deck"))
    )

190+ languages via highlight.js. Specify the language immediately after the opening fence: ```scala .

Code blocks: Python and Bash

def contrast_ratio(fg, bg):
    l1 = luminance(fg)
    l2 = luminance(bg)
    brighter = max(l1, l2)
    darker   = min(l1, l2)
    return (brighter + 0.05) / (darker + 0.05)

# WCAG AA: 4.5:1 for normal text
assert contrast_ratio('#000','#fff') >= 4.5
# Download the JAR
curl -L https://github.com/TJMSolns/\
  MD-Slides/releases/latest/\
  download/md-slides.jar \
  -o md-slides.jar

# Render with dark theme
java -jar md-slides.jar \
  render my-talk --theme dark

Long code listings

def spell(n: Int): String = n match
    case  0 => "zero"
    case  1 => "one"
    case  2 => "two"
    case  3 => "three"
    case  4 => "four"
    case  5 => "five"
    case  6 => "six"
    case  7 => "seven"
    case  8 => "eight"
    case  9 => "nine"
    case 10 => "ten"
    case 11 => "eleven"
    case 12 => "twelve"
    case 13 => "thirteen"
    case 14 => "fourteen"
    case 15 => "fifteen"
    case 16 => "sixteen"
    case 17 => "seventeen"
    case 18 => "eighteen"
    case 19 => "nineteen"
    case 20 => "twenty"
    case _  => n.toString

Code blocks over 20 lines shrink to fit, and don't count against the 12-line body limit.

Images: you write / you get

![MD-Slides logo](./images/logo.svg)

Path is relative to the .md source file. MD-Slides copies images to the output directory automatically.

Alt text is required — missing alt text is a WCAG 2.1 validation error.

For self-contained files: ![alt](data:image/svg+xml;base64,...)

MD-Slides logo

Tables: you write / you get

| Template | Max body | Used for |
|----------|----------|----------|
| `title` | — | Opening slide |
| `content` | 12 ln / 150 w | **Most** slides |
| `two-column` | 10 ln / 75 w | Comparisons |
| `diagram` | — | Mermaid charts |
| `closing` | 12 ln / 150 w | Final slide |

Column alignment: |:---:| center · |---:| right. Bold, code and links work inside cells.

TemplateMax bodyUsed for
title—Opening slide
content12 ln / 150 wMost slides
two-column10 ln / 75 wComparisons
diagram—Mermaid charts
closing12 ln / 150 wFinal slide

Per-slide background image

The diagonal stripes behind this slide come from its frontmatter. In your own deck you'd write:

template: content
background: images/stripes.png

background: takes an image path (copied into the output) or a data: URL, as here, which keeps the tour a single file. It overrides the theme background for this slide only. PNG, JPEG, SVG and WebP all work.

Speaker View

Notes, next-slide preview, and elapsed timer

Speaker notes: you write / you get

<!-- Speaker notes: Key point here.
Don't forget to mention Y.
Notes can span multiple lines. -->

Place the comment anywhere in the slide body. Press S to open speaker view.

In speaker view, you see:

  • Current slide (small preview)
  • Next slide heading
  • Your notes for the current slide
  • Elapsed timer

Notes are never visible in the main window.

Speaker view: layout, sync and timer

The speaker window shows the slide counter with ‹ › buttons, the elapsed timer, your notes, and the next slide's heading. On a phone it becomes one column.

The two windows stay in step: move in either one and the other follows (same browser, same device).

Timer: T pauses or resumes, R resets. {{timer}} in a header or footer shows it on slides.

Try it: press S, then change slides in either window.

Navigation

Every keyboard shortcut

Keyboard shortcuts — complete reference

KeyAction
→ ↓ Space Enter PgDnNext slide
← ↑ PgUpPrevious slide
Home / EndFirst / last slide
F / SFullscreen / speaker view
B / LBreak mode / save session log
GGoto: type slide number, press Enter
P / NHistory back / forward
T / RPause / reset timer

Clickers just work. On a phone or tablet, swipe, tap, or use the ‹ › buttons.

Break mode, goto and history

Break mode (B): hides the slides and pauses the timer. Press B again to resume. Set a custom image with --break-screen or breakScreen in your config.

Goto (G): type a slide number and press Enter, or pick a slide from the list. On a touch screen, tap the slide counter.

History (P / N): back and forward through the slides you actually visited.

Try it: press G, type 3, press Enter, then press P to come back here.

Any screen

Laptop, projector, phone or tablet

Touch, clickers and fullscreenTouch screens:

  • Swipe left or right
  • Tap the right side (next) or the left side (back)
  • Use the ‹ › buttons by the counter

Clickers send Page Down / Page Up or arrow keys, so they just work.

Fullscreen: press F or the ⛶ button (hidden where the browser can't do it, as on iPhone).

Try it: swipe to the next slide, then come back with a tap on the left.

Fit to the screen: a deliberately wide slide

StageInputParserValidatorRendererThemeAssetsOutput
1deck.mdFlexmarkStructureScalatagsJSONImagesindex.html
2FrontmatterSlotsDensitySpeaker viewFontsDiagramsspeaker.html
3NotesTemplatesWCAG 2.1 AANavigation JSColoursBreak imageSession log

This table is wider than a phone screen. The slide shrinks to fit, and the table re-wraps rather than overflowing.

Try it: rotate your phone, or resize this window. The slide re-fits.

Links to a slide

The address bar tracks the current slide as #N. Reloading keeps your place, and a link can open any slide:

https://www.tjm.solutions/mdslides/demo/#12

Changing slides doesn't add browser history, so Back (or a phone's back gesture) leaves the deck rather than stepping through it. Use P and N for slide history.

Try it: look at the address bar, then reload. You stay on this slide.

CLI Commands

render, display, report, config, and smart default

render: markdown to HTML

java -jar md-slides.jar render my-talk
java -jar md-slides.jar render my-talk \
  --theme dark

my-talk, my-talk.md and talks/my-talk all resolve. Errors (structure, missing alt text) stop the render; density limits only warn.

my-talk/
  index.html      ← presentation
  speaker.html    ← speaker view
  sync.js         ← plus 7 small
  …                 support scripts
  images/         ← copied assets

render: all flags

FlagDefaultPurpose
--theme NAMElightlight, dark, corporate, or a theme folder in ./themes
--no-copy-imagescopy onSkip copying image assets
--skip-accessibilitycheck onSkip WCAG 2.1 AA validation
--accessibility-report FILEoffWrite validation results to JSON
--break-screen IMAGEnoneImage shown in break mode
-i FILE / -o DIR—Explicit input and output paths

Custom themes are folders: themes/mytheme/theme.json, used as --theme mytheme.

config command

java -jar md-slides.jar config
Global Configuration:
  Location: ~/.mdslides/config.json
  Settings:
    defaults.theme: light
Project Configuration:
  Location: .mdslides/config.json
  Settings:
    theme: dark

Shows which settings are in effect and which file each came from.

Session log: how did the talk go?While presenting, MD-Slides records every slide change (and how it happened), timer pauses and breaks.

Press L to save the log. Your browser saves it as my-talk-<date>-<time>.log.json.

Try it: press L now.

java -jar md-slides.jar report my-talk

Report shows:

  • Time per slide, breaks excluded
  • The path you took
  • Breaks, and how you navigated

It finds the newest log in Downloads, or use --log FILE.

Smart default: no subcommand needed

java -jar md-slides.jar my-talk
  1. If a saved session log exists, shows its report
  2. Renders if my-talk.md changed since the last render
  3. Opens the presentation in your browser

display my-talk just opens it; render my-talk just renders.

MCP server: decks from an AI assistantmdslides-mcp.jar (on the releases page) runs MD-Slides as a Model Context Protocol server over stdio.

java -jar mdslides-mcp.jar
ToolDoes
render_deckRender a deck
validate_deckCheck a deck
list_themesList themes
get_deck_infoCount slides, templates, images

Distributing your presentationThe output directory is self-contained:

# Web server
scp -r my-talk/ user@server:/var/www/html/

# GitHub Pages
# Push my-talk/ to docs/ or gh-pages branch

# Email: zip the folder
zip -r my-talk.zip my-talk/

index.html opens in any browser — no server required.

PDF export — browser print (speaker notes stay out of the PDF):

  1. Open my-talk/index.html
  2. File → Print (Ctrl/Cmd + P)
  3. Destination → Save as PDF, then Print

Or with headless Chrome:

chromium --headless \
  --print-to-pdf=my-talk.pdf \
  my-talk/index.html

Fullscreen and browser tipsFullscreen: F, the ⛶ button, or F11 (Windows/Linux) / Ctrl+Cmd+F (Mac).

Syntax highlighting loads from a CDN, so code is highlighted when you're online. Everything else works offline.

Opening speaker view:

  1. Press S in the presentation
  2. Drag the new window to your laptop screen
  3. Navigate from either window

If speaker view won't open, allow pop-ups for the page, or serve the folder:

python3 -m http.server 8080

Themes and Configuration

Built-in themes, custom JSON themes, and four-layer config

Built-in themes

java -jar md-slides.jar render my-talk --theme light  # default
java -jar md-slides.jar render my-talk --theme dark

light — white background, dark text. dark — dark background, light text, teal accents. corporate — a neutral business look.

All pass WCAG 2.1 AA contrast. This deck's live demo uses dark.

Theme backgrounds: colour and image

In themes/mytheme/theme.json:

"background": { "color": "#1e1e1e", "image": "images/deck-bg.jpg" },
"templateBackgrounds": { "section-title": "images/section.png" }

Theme image paths are relative to the theme folder, and are copied into your output.

Priority: per-slide background: > templateBackgrounds > deck background.

Custom themes: a theme folder

themes/
  mytheme/
    theme.json
    images/
java -jar md-slides.jar render \
  my-talk --theme mytheme

theme.json needs every section: name, version, background, colors, fonts, spacing, syntax, slideCounter.

Copy a complete example from the docs' Themes page and change what you need.

Per-template configuration in themes

"templateConfig": {
  "section-title": {
    "header": "{{pageNumber}}/{{totalPages}}",
    "verticalAlign": "top"
  },
  "closing": {
    "footer": "{{title}} · {{date}}"
  }
}
KeyEffect
headerHeader text, with tokens
footerFooter text, with tokens
verticalAligntop center bottom
colorsColour overrides

Every slide of that template gets it, with no frontmatter needed.

Feature Tour — Slide {{pageNumber}} of {{totalPages}}

Per-slide header and footer: live demo

This slide's frontmatter:

header: Feature Tour — Slide {{pageNumber}} of {{totalPages}}
footer: {{title}} · {{date}} · {{timer}}

Tokens: {{pageNumber}} {{totalPages}} {{timer}} {{title}} (the deck title) {{date}} (today).

Try it: check the header and footer on this slide. The timer ticks.

Vertical alignment

Content position per slide via frontmatter:

template: content
vertical-align: top

Three options: top · center (default) · bottom

Useful for sparse slides where centered text looks visually unbalanced, or for slides with code blocks that benefit from a predictable top position.

Four-layer configuration

Highest wins: CLI flags > project .mdslides/config.json > global ~/.mdslides/config.json > defaults

{ "theme": "dark", "outputDir": "dist", "breakScreen": "images/break.png" }
{ "defaults": { "theme": "light" }, "paths": { "themesDir": "~/themes" } }

The first is a project config; the second a global one, where settings go under defaults.

Validation

All errors collected and shown together

What gets validated

Errors (the render stops): missing required slots, unknown templates, a two-column slide without exactly one ---column---, images without alt text, image files that don't exist.

Warnings (the render continues): headings over 80 characters, bodies over 12 lines or 150 words, columns over 10 lines or 75 words, theme colours below WCAG 2.1 AA contrast.

Validation output: everything at once

✗ Validation failed with 4 problem(s):
  - Slide 3: Structure Error - Required slot 'heading' is missing
  - Slide 3: Structure Error - Required slot 'body' is missing
  - Slide 4: Unknown template 'bogus'. Valid templates: title, content, diagram, closing, section-title, two-column
  - Slide 5: image file 'images/lamp.png' does not exist (looked for images/lamp.png)
✓ Validation passed with 1 warning(s):
  - Slide 2: Density Warning in 'body' slot - Content exceeds
    recommended line limit (actual: 17, guidance: 12 lines)

Validation reports every problem on every slide in one pass, before anything is written, so you fix them all at once. An image without alt text is reported by name: ![what it shows](photo.png).

MD-Slides

Markdown → self-contained HTML presentations

Open the source. examples/feature-tour.md produced every slide in this deck.