Skip to content

Getting started

You need Node 20.19 or newer (Vite 8’s floor) and a package manager — npm, pnpm, Yarn or Bun all work.

Terminal window
npm create @kadal/deck@latest my-talk
cd my-talk
npm install
npm run dev

Vite prints the address; open it and press ? for the keys. Edit deck.mdx and the slide you are looking at updates in place — each slide is its own module, so a change re-renders one slide, not the deck.

my-talk/
├── deck.mdx the talk: headmatter, then slides split at ---
├── deck.config.ts engine settings that override the headmatter
├── glossary.yaml optional: term cards (see Glossary term cards)
├── index.html the page Vite serves
├── src/
│ ├── main.tsx mounts <Deck>, the pages and the live provider
│ ├── components.tsx your own components, usable in deck.mdx without imports
│ └── styles.css your CSS on top of the theme
├── public/ images, video, posters — served as-is
├── vite.config.ts the deck() plugin and React
└── package.json

src/main.tsx is short, and the whole engine hangs off it:

import { Deck } from "@kadal/deck";
import "@kadal/deck/styles.css";
import "@kadal/deck/themes/minimal.css";
import "@kadal/deck/backgrounds";
import { AudiencePage } from "@kadal/deck/audience";
import { LiveProvider } from "@kadal/deck/live";
import { NotesPage, PresenterPage, StatsPage } from "@kadal/deck/presenter";
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import manifest from "virtual:deck";
import { config } from "../deck.config.ts";
import { components } from "./components.tsx";
import "./styles.css";
createRoot(document.getElementById("root")!).render(
<StrictMode>
<Deck
manifest={manifest}
config={config}
components={components}
pages={{
presenter: PresenterPage,
audience: AudiencePage,
notes: NotesPage,
stats: StatsPage,
}}
>
<LiveProvider />
</Deck>
</StrictMode>,
);

virtual:deck is what the Vite plugin makes of deck.mdx: the configuration, one lazily loaded module per slide and per slide’s notes, and the glossary.

vite.config.ts
import { deck } from "@kadal/deck/vite";
import react from "@vitejs/plugin-react";
import { defineConfig } from "vite";
export default defineConfig({
plugins: [deck({ entry: "deck.mdx" }), react()],
});

Every deck is a small app with several views, all served from the same build:

Path What
/ the deck
/presenter the presenter view: current and next slide, notes, timer, tools, the room
/join the audience page for phones
/notes every note on one page
/stats views, time per slide, sessions, questions
/?print-pdf every slide as a page, for printing
/?view=scroll the deck as one scrolling page
Terminal window
npm run build # static files in dist/

dist/ is a static site: put it on any host. To get a link and a live room without hosting anything, publish it:

Terminal window
npx kadal-deck login
npx kadal-deck publish

The live features — following, polls, questions, reactions — talk to a room server at live.api (/api by default). In development, either proxy /api to a room you host (Self-hosting the room), or set live: { api: null } in deck.config.ts for an offline deck. A published deck on deck.kadal.cc gets its room automatically.