Hello Reveal.js + MkDocs — From Zero to Expert

6 minute read

Today we are going to empower your content with MkDocs & Reveal.js for great Presentations from a Single Markdown Source. Keeping docs and slides in sync is hard. You update a code sample in the docs, forget the deck, and… hello drift. Let’s fix that. In this post, you’ll author one Markdown file and publish it as both:

  • A page in your MkDocs site.
  • An interactive Reveal.js presentation (plus a PDF export). All of this will be automated using Pandoc, a Makefile, and a GitHub Actions workflow. We’ll use IBM watsonx Agentic AI for the example topic, but the pipeline works for anything.

What you’ll build (Level 0)

A workflow where one Markdown file renders in three places:

  • MkDocs page
  • Reveal.js HTML deck
  • PDF (via DeckTape)

All generated from the same source and deployed by GitHub Actions.


Prerequisites

  • Python 3.11+ (the project uses uv for fast virtualenv & dependency management)
  • Pandoc (Markdown → Reveal.js conversion)
  • Docker (for DeckTape → PDF export)
  • Optional: Node.js + npm (only if you want to run reveal’s dev server; not required for this flow)

Tip: make install runs a bootstrap to help install system tools and prepares the Python env via uv.


Repository structure

.
├─ docs/
│  ├─ index.md
│  ├─ watsonx-agentic-ai.md        # single source of truth (Markdown)
│  └─ slides/                       # generated artifacts
│     ├─ watsonx-agentic-ai.html   # generated by Pandoc
│     └─ watsonx-agentic-ai.pdf    # generated by DeckTape
├─ mkdocs.yml
├─ scripts/
│  ├─ generate_slides.sh
│  ├─ export_pdf.sh
│  └─ bootstrap.sh                  # optional helper to install tools
├─ Makefile
└─ .github/workflows/deploy.yml

Why docs/slides/? Because mkdocs build copies everything under docs/ into site/. Generating into site/ first is risky — the next mkdocs build would delete your custom files.


Level 1 — Author the single Markdown

Pandoc+Reveal support header attributes right on the heading line. MkDocs happily ignores/keeps those as HTML attributes.

Create docs/watsonx-agentic-ai.md:

---
title: "IBM watsonx & Agentic AI"
author: "Ruslan Magana Vsevolodovna"
date: "2025-11-12"
---

# Introduction to Agentic AI
This document/presentation explores the emerging field of Agentic AI, with a focus on IBM watsonx capabilities.

## What is Agentic AI? {data-transition="slide"}
Agentic AI refers to systems designed to autonomously perceive, reason, plan, and act in complex environments to achieve specific goals.

- Autonomy
- Goal-oriented behavior
- Adaptability & learning

**Key idea:** Beyond prompt→response; agents can proactively plan & act.

## IBM watsonx.ai for Agents {data-transition="fade"}
IBM watsonx.ai provides foundational models and tooling to build, deploy, and govern Agentic AI solutions.

- Foundation models
- Prompt tooling
- Governance (data, risk, policies)

```python
# Illustrative code — check IBM docs for the latest SDKs & model IDs
# from ibm_watsonx_ai import Model
# model = Model(model_id="...")  # configure auth & project first
# print(model.generate("Explain Agentic AI in one sentence."))
```

::: notes
Speaker note: call out governance and evaluation early for enterprise readers.
:::

## Building an Agent {data-background-color="#0f172a" data-transition="convex" data-background-transition="zoom"}
1. Define the goal (task & success metrics)  
2. Select tools (APIs, DB, search, calculator, internal services)  
3. Orchestrate control flow (tool choice, planning, retries)  
4. Evaluate, observe, iterate (offline & online)

```bash
# Conceptual CLI flow (placeholder)
agent-builder create my-wx-agent --goal "answer customer queries" --llm "..."
agent-builder deploy my-wx-agent --env production
```

## Ethical Considerations {data-transition="fade"}
- Transparency & observability  
- Bias & fairness checks  
- Accountability & human oversight

Tips

  • ## creates a new slide with --slide-level=2; ### makes a vertical sub-slide.
  • Speaker notes: wrap content in ::: notes ... :::.
  • Fragments: wrap bullet text in <span class="fragment">…</span> or use a class.

Level 2 — Generate the Reveal.js HTML

Use a small helper script that auto-picks the right Reveal version depending on your Pandoc version (v3 CDN for older Pandoc, v4 CDN for modern Pandoc). This is the standard, low-friction approach.

scripts/generate_slides.sh:

#!/usr/bin/env bash
set -euo pipefail

ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.."; pwd -P)"
SOURCE_MD="${SOURCE_MD:-${ROOT}/docs/watsonx-agentic-ai.md}"
OUT_DIR="${OUT_DIR:-${ROOT}/docs/slides}"
DECK_NAME="${DECK_NAME:-$(basename "${SOURCE_MD%.*}")}"
HTML_OUT="${OUT_DIR}/${DECK_NAME}.html"

command -v pandoc >/dev/null 2>&1 || { echo "Pandoc not found: https://pandoc.org/installing.html" >&2; exit 1; }
PANDOC_VER="$(pandoc -v | head -n1 | awk '{print $2}')"
ver_ge(){ [ "$(printf '%s\n' "$2" "$1" | sort -V | head -n1)" = "$2" ]; }

mkdir -p "${OUT_DIR}"

if pandoc --help | grep -q -- "--embed-resources"; then EMBED_FLAG="--embed-resources"; else EMBED_FLAG="--self-contained"; fi
REVEAL_THEME="${REVEAL_THEME:-league}"
REVEAL_TRANSITION="${REVEAL_TRANSITION:-slide}"

if ver_ge "${PANDOC_VER}" "2.12"; then
  REVEAL_VERSION="${REVEAL_VERSION:-4.6.0}"
  REVEAL_URL="https://cdn.jsdelivr.net/npm/reveal.js@${REVEAL_VERSION}"
  echo "Using Reveal v4 CDN (${REVEAL_URL}) with Pandoc ${PANDOC_VER}"
else
  REVEAL_URL="https://cdnjs.cloudflare.com/ajax/libs/reveal.js/3.9.2"
  echo "Using Reveal v3 CDN (${REVEAL_URL}) with Pandoc ${PANDOC_VER}"
fi

echo "Generating Reveal.js slides → ${HTML_OUT}"
pandoc \
  --standalone \
  --to=revealjs \
  --slide-level=2 \
  ${EMBED_FLAG} \
  --variable "revealjs-url=${REVEAL_URL}" \
  --variable "theme=${REVEAL_THEME}" \
  --variable "transition=${REVEAL_TRANSITION}" \
  --variable slideNumber=true \
  --variable hash=true \
  --metadata=pagetitle:"IBM watsonx & Agentic AI" \
  -o "${HTML_OUT}" \
  "${SOURCE_MD}"

echo "✅ Slides generated at ${HTML_OUT}"

Run it:

make slides

Level 3 — Export the deck to PDF (DeckTape)

DeckTape renders your HTML in a headless browser.

scripts/export_pdf.sh:

#!/usr/bin/env bash
set -euo pipefail

DECKTAPE_IMAGE="${DECKTAPE_IMAGE:-astefanutti/decktape:latest}"
SLIDE_SIZE="${SLIDE_SIZE:-1920x1080}"
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.."; pwd -P)"
HTML_REL="docs/slides/watsonx-agentic-ai.html"
PDF_REL="docs/slides/watsonx-agentic-ai.pdf"
HTML_HOST="${ROOT}/${HTML_REL}"
PDF_HOST="${ROOT}/${PDF_REL}"

command -v docker >/dev/null 2>&1 || { echo "Docker required for PDF export" >&2; exit 1; }
[ -f "${HTML_HOST}" ] || { echo "Missing HTML: ${HTML_HOST}. Run generate_slides.sh first." >&2; exit 1; }

docker image inspect "${DECKTAPE_IMAGE}" >/dev/null 2>&1 || docker pull "${DECKTAPE_IMAGE}"

MOUNT_POINT="/work"
HTML_URL="file://${MOUNT_POINT}/${HTML_REL}?print-pdf"
PDF_PATH="${MOUNT_POINT}/${PDF_REL}"
mkdir -p "$(dirname "${PDF_HOST}")"

echo "Exporting PDF → ${PDF_HOST}"
docker run --rm -t \
  -v "${ROOT}:${MOUNT_POINT}" \
  "${DECKTAPE_IMAGE}" \
  reveal \
  --size "${SLIDE_SIZE}" \
  --slides 1- \
  --load-pause 1500 \
  "${HTML_URL}" \
  "${PDF_PATH}"

echo "✅ PDF generated at ${PDF_HOST}"

Run it:

make pdf

DeckTape opens your deck with ?print-pdf so Reveal’s print stylesheet applies.


Level 4 — Minimal MkDocs config

mkdocs.yml:

site_name: Hello Reveal.js + MkDocs
site_url: https://<YOUR_USERNAME>.github.io/<YOUR_REPO_NAME>/

theme:
  name: material

nav:
  - Home: index.md
  - IBM watsonx & Agentic AI: watsonx-agentic-ai.md
  - Slides:
      - HTML Deck: slides/watsonx-agentic-ai.html
      - PDF Deck: slides/watsonx-agentic-ai.pdf

markdown_extensions:
  - attr_list
  - admonition
  - toc:
      permalink: true

Local preview:

make serve
# → http://127.0.0.1:8000/

Level 5 — CI/CD with GitHub Actions

This builds slides, exports PDF, builds MkDocs, and deploys to GitHub Pages on every push.

.github/workflows/deploy.yml:

name: Build & Deploy — MkDocs + Reveal.js + PDF

on:
  push:
    branches:
      - main
      - master
  workflow_dispatch:

permissions:
  contents: read
  pages: write
  id-token: write

concurrency:
  group: "pages"
  cancel-in-progress: true

jobs:
  build:
    name: Build site, slides & PDF
    runs-on: ubuntu-latest

    steps:
      - name: Checkout
        uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Configure Pages
        uses: actions/configure-pages@v5

      # --- Slides (Pandoc) ---
      - name: Install Pandoc
        run: |
          sudo apt-get update
          sudo apt-get install -y pandoc

      - name: Generate Reveal.js HTML slides
        run: bash scripts/generate_slides.sh

      # --- PDF (DeckTape via Docker) ---
      # Run container with the same UID:GID as the runner to avoid EACCES on bind mount
      - name: Export PDF with DeckTape
        run: |
          mkdir -p docs/slides
          uid="$(id -u)"; gid="$(id -g)"
          docker run --rm -t \
            -u "${uid}:${gid}" \
            -v "$PWD":/work \
            astefanutti/decktape \
            reveal \
            --size 1920x1080 \
            --slides 1- \
            "file:///work/docs/slides/watsonx-agentic-ai.html?print-pdf" \
            "/work/docs/slides/watsonx-agentic-ai.pdf"

      # --- MkDocs (via uv) ---
      - name: Install uv (Python env)
        run: curl -LsSf https://astral.sh/uv/install.sh | sh

      - name: Add uv to PATH
        run: echo "$HOME/.local/bin" >> $GITHUB_PATH

      - name: Install Python 3.11 and deps
        run: |
          uv python install 3.11
          uv sync

      - name: Build MkDocs site
        run: uv run mkdocs build --strict

      # --- Upload built site to the Pages artifact bucket ---
      - name: Upload artifact
        uses: actions/upload-pages-artifact@v3
        with:
          path: site

  deploy:
    name: Deploy to GitHub Pages
    needs: build
    runs-on: ubuntu-latest
    environment:
      name: github-pages
      url: $

    steps:
      - name: Deploy
        id: deployment
        uses: actions/deploy-pages@v4

After the workflow finishes, your content is available at:

  • https://<YOUR_USERNAME>.github.io/<YOUR_REPO_NAME>/ (site)
  • …/slides/watsonx-agentic-ai.html (deck)
  • …/slides/watsonx-agentic-ai.pdf (PDF)

Level 6 — Power‑ups

  • Slide numbers & deep links: -V slideNumber=true -V hash=true (already in the script)
  • Vertical stacks: Use ### under a ##
  • Code highlight: Pandoc ships highlight styles; you can add --highlight-style=pygments.
  • Math: add --mathjax to Pandoc; then use $…$ or $$…$$.
  • Images: regular Markdown images work in both docs & slides. Prefer relative paths under docs/.
  • Backgrounds / transitions: header attributes, e.g. {data-background-color="#111827" data-transition="fade"}.

Troubleshooting

PDF is blank / styling off Ensure ?print-pdf is appended in the DeckTape URL and that your deck loads over the CDN (not local node_modules).

“File …/css/reset.css not found” Your Pandoc template expects Reveal v3. Use the version-aware generate_slides.sh above (it selects a v3 CDN for older Pandoc).

Assets 404 in CI Don’t rely on a local path. Use CDN revealjs-url as shown.

MkDocs can’t find slides Verify the files exist under docs/slides/ before mkdocs build, and that mkdocs.yml nav: paths match.


Local workflow recap

make install   # bootstrap tools + uv env
make slides    # generate HTML deck
make pdf       # export PDF via DeckTape (Docker)
make serve     # run MkDocs locally (http://127.0.0.1:8000)

This is the standard, low-maintenance setup: one Markdown → docs page + slides + PDF, locally and in CI, with minimal moving parts.

Posted:

Leave a comment