Write presentations as plain HTML files. Each slide is a standalone .html file — no framework, no build step, just open your editor.
npm install -g deckthisOr run without installing:
npx deckthis <folder>- Create a folder and add slide HTML files:
my-talk/
01.html
02.html
03.html
- Start the dev server:
deckthis my-talk- Open
http://localhost:39200in your browser.
Slides are served in alphabetical filename order. Edit any file and the browser reloads automatically.
| Input | Action |
|---|---|
→ / ↓ / Space |
Next slide |
← / ↑ |
Previous slide |
| Swipe left/right | Mobile nav |
my-talk/
01.html # Slides — discovered alphabetically
02.html
03.html
_overlay.html # Foreground layer (page numbers, logo…) — optional
_underlay.html # Background decoration layer — optional
deckthis.config.ts # Config file — optional
- Files prefixed with
_are excluded from the slide list and used as overlay / underlay. - The config file is named
deckthis.config.tsand usesdefineConfigfor type safety.
import { defineConfig } from "deckthis";
export default defineConfig({
// Control slide order — receives the auto-discovered list, returns the final order
order: (discovered) => {
const intro = discovered.find((s) => s === "/intro.html");
const rest = discovered.filter((s) => s !== "/intro.html");
return intro ? [intro, ...rest] : discovered;
},
// Extra static asset directories (absolute paths, searched in order after the deck folder)
assets: ["/path/to/theme-assets"],
// Transform each slide's HTML before it is served (not applied to overlay/underlay)
beforeEach: (html, ctx) => {
return html.replace("</head>", '<link rel="stylesheet" href="/theme.css"></head>');
},
// Explicitly set overlay / underlay (defaults to auto-detecting _overlay.html / _underlay.html)
overlay: "/_overlay.html",
underlay: "/_underlay.html",
});| Field | Type | Description |
|---|---|---|
order |
(discovered: string[]) => string[] |
Customize slide order; can insert plugin-provided extra pages |
assets |
string[] |
Absolute directory paths for static asset fallback lookup |
beforeEach |
(html, ctx) => string | Promise<string> |
Transform a slide's HTML before serving |
overlay |
string |
Foreground iframe URL; auto-detected from _overlay.html |
underlay |
string |
Background iframe URL; auto-detected from _underlay.html |
_overlay.html sits above all slides (pointer-events disabled by default), _underlay.html sits below.
On every slide change, deckthis broadcasts a postMessage to both frames:
window.addEventListener("message", (e) => {
if (e.data?.type !== "deckthis:slide-change") return;
const { current, total, title } = e.data;
// current: 1-based slide number
// total: total slide count
// title: from <meta name="deckthis:title"> or <title>
});Add metadata to any slide:
<meta name="deckthis:title" content="Introduction" /> <meta name="deckthis:section" content="01" />A plugin is a plain function that wraps user config and returns a merged config:
// _plugin/my-theme.ts
import type { DeckthisConfig } from "deckthis";
export function myTheme(userConfig: DeckthisConfig = {}): DeckthisConfig {
return {
overlay: "/_plugin/overlay.html",
assets: ["/path/to/_plugin"],
order: (discovered) => [
"/_plugin/cover.html",
...(userConfig.order ? userConfig.order(discovered) : discovered),
"/_plugin/thanks.html",
],
beforeEach: async (html, ctx) => {
const base = userConfig.beforeEach ? await userConfig.beforeEach(html, ctx) : html;
return base.replace("</head>", '<link rel="stylesheet" href="/_plugin/theme.css"></head>');
},
};
}// deckthis.config.ts
import { myTheme } from "./_plugin/my-theme";
export default myTheme({
// pass your own config; the plugin wraps it
});Plugin conventions:
- Place plugin files under a
_-prefixed directory (e.g._plugin/) so they are not treated as slides. - Plugins are pure functions — they only transform config, with no knowledge of deckthis internals.
- When wrapping
orderorbeforeEach, call the user's version first, then apply plugin logic.
deckthis <folder> # Start dev server (default port 39200)
deckthis <folder> --port 3000 # Use a custom port
deckthis demo list # List built-in demos
deckthis demo <name> # Copy a demo to the current directory
deckthis img-gen "prompt" # Generate a PNG image
deckthis img-edit in.png "prompt" # Edit an existing PNG image
deckthis skill # Copy the AI coding skill to a skills directory
deckthis export <folder> # Export to PPTX (presentation.pptx)
deckthis export <folder> -o my-talk.pptx # Custom output path
deckthis export <folder> --width 1920 --height 1080 # Viewport size (default: 1920×1080)
deckthis export <folder> --scale 2 # Higher resolution screenshots
deckthis export <folder> --wait 3000 # Wait longer before each screenshotThe export command captures each slide at full resolution and packages them into a .pptx file. Each slide becomes a full-bleed image in the deck, preserving your CSS, fonts, and all three rendering layers (underlay + slide + overlay).
export requires playwright-chromium to be installed in your project. Install it once:
npm install -D playwright-chromium
npx playwright install chromiumThen export:
deckthis export my-talk -o my-talk.pptxYou can tune export timing with --wait <ms> when a deck needs extra time for CSS transitions or delayed overlay updates. The default is 1500, so you usually do not need to set it explicitly.
You can also provide default export settings in deckthis.config.ts:
import { defineConfig } from "deckthis";
export default defineConfig({
export: {
width: 1504,
height: 831,
wait: 3000,
},
});CLI flags take precedence over config.export.
deckthis includes image generation commands migrated from tiny-image-gen.
Supported commands:
deckthis img-gen "a minimal geometric poster with bold orange and black shapes"
deckthis img-edit ./input.png "turn this into a cleaner flat illustration poster"Common options:
--sizesupports1024x1024,1792x1024, and1024x1792; default is1792x1024--outis optional; if omitted, the command writes./output-<timestamp>.png
If you want to use image generation, run the command directly and follow the CLI prompt to configure the required DECKTHIS_IMG_* environment variables.
Notes:
- supported sizes are
1024x1024,1792x1024, and1024x1792 - output is always a single PNG image
- if
--outis omitted, the command writesoutput-<timestamp>.pngin the current working directory - before the request starts, the CLI prints
Generating image, this may take some time... - if required environment variables are missing, the CLI prints the exact
export ...commands to run foropenaiorazure
Inside deckthis.config.ts, call getDeckDir() to get the absolute path of the deck folder. Useful for constructing asset paths dynamically:
import { defineConfig, getDeckDir } from "deckthis";
import path from "node:path";
export default defineConfig({
assets: [path.join(getDeckDir(), "_assets")],
});After installing, copy and run the built-in demos:
deckthis demo basic # Three slides, minimal setup
deckthis demo with-config # Demonstrates all defineConfig options
deckthis demo with-plugin # Demonstrates the plugin pattern
cd basic && deckthis .pnpm install
# Build the browser runtime (required before first run or after editing core)
pnpm build:core
# Run all tests
pnpm test
# Run tests for a specific package
pnpm test:core
pnpm test:clipackages/
deckthis-core/ # Browser runtime (SlideDeck)
src/core.ts # iframe layout, keyboard/touch navigation, overlay/underlay
src/wrapper.ts # IIFE entry — fetches /__deckthis/config and initialises SlideDeck
deckthis-cli/ # Dev server + CLI
src/cli.ts # CLI entry, port management, file-watch restart
src/dev-server.ts # Hono server, SSE hot-reload, assets/beforeEach pipeline
src/load-config.ts # Loads deckthis.config.ts via dynamic import
src/types.ts # DeckthisConfig, defineConfig, getDeckDir