User-created slide templates

This section is for people comfortable with code. You can make and present slides without it.

Templates define slide structure; themes supply typography and colors. Every built-in layout and every deck-local template uses the same registry, manifest, property resolver, and slide frame. A local template requires no framework edit.

Example
my-talk/
  slides.md
  extensions/
    comparison/
      extension.toml
      layout.jsx
      styles.css       optional
      starter.md       optional

A template is one kind of extension; themes and palettes use the same folder and manifest rules. The folder name is the layout: value. Names must be lowercase letters, digits and hyphens, beginning with a letter. A local template cannot use a built-in's name. Manifests are checked when loading, building, listing or validating a deck. The dev server reloads when extension files are changed, added or removed.

Manifest#

Example
schema = 1
kind = "template"
id = "comparison"
title = "Side-by-side comparison"

[regions.body]
description = "Shared heading"

[regions.left]
required = true

[regions.right]
required = true

[properties.emphasis]
type = "string"
enum = ["none", "left", "right"]
default = "none"

regions must include body, which contains unassigned slide Markdown. Authors fill additional regions using :::slot left, etc. A region can declare required: true and a human-readable description. Unknown region names and missing required content are validation errors.

properties describes values under slide metadata props:. Supported types: string, number, integer, boolean, array, object. The schema supports required, default, enum, numeric minimum/maximum, array items (a [properties.name.items] sub-table), minItems/maxItems, and title and description for editors. This is a small schema subset, not full JSON Schema. Defaults are copied for each slide. Unknown properties and invalid values are errors. Built-ins also accept their legacy top-level properties such as image:.

An optional frame selects standard (default), title, chapter, or none. The last omits header/footer chrome; it still retains the slide container, footnotes, ID, and navigation behavior.

Renderer#

layout.jsx default-exports a Preact component. It renders the slide body:

Example
import { h } from 'preact'
import { MarkdownRegion } from 'mdeck/template-api'

export default function Comparison({ regions, props }) {
  return <div class="slide-body">
    <MarkdownRegion region={regions.body} />
    <div class="comparison-options">
      <MarkdownRegion region={regions.left} />
      <MarkdownRegion region={regions.right}
        class={props.emphasis === 'right' ? 'is-emphasized' : ''} />
    </div>
  </div>
}

The component receives regions, resolved typed props, slide meta, id, default-body content, deckConfig, index, total, and manifest. Use preact/hooks for interactive templates. MarkdownRegion renders Markdown and hydrates both built-in and deck-local components. It exposes a data-region attribute for integrations. Footnotes are numbered across all regions and placed in the shared frame.

The framework wraps the result in SlideFrame; do not add a second <section> or frame in your renderer. SlideFrame, MarkdownRegion, and HtmlContent are also exported from mdeck/template-api for integrations. The API import resolves through mdeck's build plugin, including when the deck is outside the framework.

Keep CSS scoped to .slide--comparison and use theme variables:

Example
.slide--comparison .comparison-options {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 40px;
}
.slide--comparison .is-emphasized { color: var(--accent); }

JSX imports of assets and dependencies follow the same build rules as deck-local components. Template CSS is included in both bundle and self-contained builds. Local paths in Markdown images, media src, and image:/props.image are handled by asset packaging. For other custom asset properties, import assets in JSX so Vite can track them; arbitrary string properties are not treated as file paths. Renderers must consume their declared regions; the framework validates the manifest and authored values, but cannot prove what arbitrary JSX renders.

Starters and discovery#

starter.md is an editable slide preset. It should contain explicit :::meta and placeholder region content, without a leading slide separator. layout.jsx, styles.css and starter.md are picked up automatically from the template folder; use a [files] table in the manifest to point at different names. mdeck new lists built-in and local starters found beside the requested output deck.

In your terminal
mdeck templates slides.md
mdeck templates slides.md --json
mdeck templates slides.md --starter comparison
mdeck extensions slides.md
mdeck check slides.md --strict

The JSON listing is the serializable manifest registry, suitable for completion, template galleries, or future editor inspectors. Listing and validation do not execute template JSX. Packages and a visual editor are not implemented here.

Built-in layouts are ordinary templates under the framework's assets/extensions/templates/ folder and use the same mdeck/template-api import, so they double as worked examples.

See the runnable custom template example:

In your terminal
mdeck dev examples/custom-templates/slides.md
mdeck build examples/custom-templates/slides.md