Migrate a Slidev deck
Kadal Deck reads much of Slidev’s syntax as it is: the separators, headmatter and frontmatter, block
frontmatter, defaults, comment notes with [click] markers, src: includes with ranges, slots,
the layout names, scoped <style>, the code-fence options, magic move, code groups, snippets,
TwoSlash, Monaco, Mermaid and PlantUML fences, stepped maths, Comark (MDC), icon tags, v-drag,
and the five official themes. What differs is underneath: a Kadal deck is MDX with React components,
where a Slidev deck is Markdown with Vue. The importer does that translation for you.
Import a deck in one command
Section titled “Import a deck in one command”From the folder that holds your Slidev project:
npx kadal-deck import slidev ./my-talkpnpm exec kadal-deck import slidev ./my-talkyarn kadal-deck import slidev ./my-talkbunx kadal-deck import slidev ./my-talkIt reads ./my-talk (a folder, or its slides.md) and writes a new deck folder beside it,
./my-talk-kadal, made the way kadal-deck new makes one. Nothing in the Slidev project is changed,
installed or run. Then it runs kadal-deck check on the result and prints a summary:
Imported my-talk/slides.md into my-talk-kadal. 4 slides, 4 (100%) with nothing to report. theme: seriph → seriph (@kadal/theme-seriph) report: 0 errors, 1 warnings, 0 notes — IMPORT-REPORT.md check: 0 errors, 1 warnings (kadal-deck check in the folder lists them); 2 packages to install
Next:
cd my-talk-kadal npm install npm run dev| Flag | What it does |
|---|---|
--out <dir> (-o) |
where the new deck goes (default <folder>-kadal) |
--force |
write into a folder that isn’t empty |
--install |
install the new deck’s packages straight away; --pm npm|pnpm|yarn|bun picks the package manager |
--json |
one JSON object on stdout, for scripts |
Slidev’s own starter deck converts with 16 of its 17 slides needing nothing further.
What you get
Section titled “What you get”| File | From |
|---|---|
deck.mdx |
slides.md, converted; files it includes with src: land in pages/, converted too |
src/components/*.tsx, src/layouts/*.tsx, src/global-top.tsx… |
your .vue components, layouts and global layers, as React |
src/custom-nav-controls.tsx |
custom-nav-controls.vue |
src/components.tsx |
registers the converted components, so slides use them without imports |
src/slidev.css |
the utility classes your slides use (UnoCSS spellings), at Kadal’s scale |
src/slidev.tsx |
the few helpers converted components need (Slidev’s click handlers, for instance) |
src/styles/*.css |
your styles/, with @apply resolved |
public/, snippets/ |
copied as they are |
package.json |
the starter’s, plus the Kadal port of your theme, an @iconify-json/<set> for each icon set, and your export settings |
IMPORT-REPORT.md |
everything that needs a look, slide by slide, worst first |
What converts
Section titled “What converts”- The headmatter: your theme becomes its Kadal port (
default→classic,apple-basic→crisp),colorSchema→colorScheme, transitions (Slidev’s way out of a slide becomes Kadal’s way in), drawings (persist: true→persist: "files"), canvas size, fonts,defaults,mdc: true→comark: true, the Monaco type settings (monacoTypesSource,monacoTypesAdditionalPackages,monacoTypesIgnorePackages) and the export options. - Clicks: every
v-click,v-after,v-clicks,v-switch,v-mark,v-motionandv-drag, with their values and modifiers, and their component forms. Click counts are written as explicit steps wherever Slidev and Kadal count differently. - Template values:
{{ $slidev.nav.currentPage }}and the other navigation values,{{ $clicks }},$clicksContext.currentandv-if="$clicks > n"chains (as<ClickCount>), the slide’s<script setup>constants,v-forandv-ifthat can be decided at import,:propbindings and$slidev.navclick handlers. - Slidev’s built-in components by their Kadal names (table below), icons, code-fence options.
- The project’s Vue files: components, layouts,
global-top.vue,global-bottom.vueandcustom-nav-controls.vuebecome TSX. State (ref), props and simple handlers carry over. - Lengths are rescaled from Slidev’s 980 px canvas to Kadal’s 1920 × 1080 stage, so the slides keep their proportions.
What the report asks you to look at
Section titled “What the report asks you to look at”IMPORT-REPORT.md gives each item a severity: an error means the slide doesn’t show what the
Slidev one did (the source is kept beside it in an MDX comment, {/* slidev: … */}); a warning
means it shows, but differently; a note means it was carried over in Kadal’s spelling, or
dropped because Kadal does it another way. Typical entries:
- a Vue component with real logic (watchers, emits, composables) becomes a stub that renders its
markup, with a
TODOat the top; - classes that are neither UnoCSS utilities the importer knows nor defined in your styles;
{monaco-run}blocks that import something: a package import loads from esm.sh when the block runs, a local file does not, so inline it or register a runner for it.
Work through the errors first, then run npx kadal-deck checkpnpm exec kadal-deck checkyarn kadal-deck checkbunx kadal-deck check and npm run devpnpm devyarn devbun run dev and look at every
slide.
The converter is also a library, convertSlidev from @kadal/deck/import, a pure function for
tools that convert in the browser.
Move a deck by hand
Section titled “Move a deck by hand”- Start a deck:
npm create @kadal/deck@latest my-talkpnpm create @kadal/deck my-talkyarn create @kadal/deck my-talkbun create @kadal/deck my-talk. - Copy
slides.mdtomy-talk/deck.mdx, andpublic/across. - Change the theme’s name if it differs (below). Every theme ships with the engine.
- Turn Slidev’s directives and Vue syntax into their Kadal spellings (below).
- Run
npx kadal-deck checkpnpm exec kadal-deck checkyarn kadal-deck checkbunx kadal-deck check. It names, with file and line, everything that won’t compile or won’t do what it looks like, including Slidev syntax that compiles but does nothing (<div v-click>).
Themes
Section titled “Themes”Slidev’s five official themes are Kadal packages, with the same layouts, layout names, frontmatter,
slots, components, themeConfig keys and colour schemes. Only the name changes for two of them:
Slidev theme: |
Kadal theme: |
Its package, to pin a version (optional) |
|---|---|---|
default |
classic |
@kadal/theme-classic |
seriph |
seriph |
@kadal/theme-seriph |
apple-basic |
crisp |
@kadal/theme-crisp |
bricks |
bricks |
@kadal/theme-bricks |
shibainu |
shibainu |
@kadal/theme-shibainu |
none |
none |
built in |
theme: default is not a Kadal theme: Kadal’s default is minimal, so write classic for Slidev’s
look. The ports are MIT-licensed like the originals, and each package’s LICENSE keeps the Slidev
team’s copyright notice. A community theme has no Kadal port: pick one from the
gallery, or write one. On deck.kadal.cc every theme in the
gallery is there without installing anything.
The same in both
Section titled “The same in both”---separates slides; the first YAML block is the headmatter; YAML after a separator is that slide’s frontmatter; ayamlcode block first in a slide is its frontmatter too.defaults:,src: ./part.md#2-5,hide,hideInToc,level,routeAlias,preload,zoom,clicks,clicksStart,transition,layout,class,background.aspectRatioandcanvasWidthset the stage size. (Kadal authors at 1920 × 1080; sizes in a Slidev theme are scaled for it.)- Notes in a comment at the end of a slide,
[click]and[click:3]in them,notesAutoRuby. ::right::,::left::,::default::slots;two-cols-headerand every other built-in layout name.- A
<style>in a slide is scoped to it;<style global>is not. - Code:
{2|3-5|all},{lines:true,startLine:5,maxHeight:'200px'},[file.ts],// [!code ++],::code-group,<<< @/snippets/x.ts#region,twoslash,{monaco},{monaco-diff},{monaco-run},{monaco-write}, andmd magic-moveblocks with highlight steps in their fences. ```mermaid {theme: 'neutral'},```plantuml,$$ {1|3|all},katexandmermaidsettings,plantUmlServer.mdc: true(Kadal’s name iscomark: true) and its syntax. See Comark syntax.fonts: { sans, serif, mono, weights, italic, provider, local },themeConfig,addons,download,timer,duration,contextMenu,remoteAssets,seoMeta,favicon.- Icon tags such as
<mdi-account />and<carbon-logo-github />;v-drag,<v-drag>and<v-drag-arrow>. global-top,global-bottomandcustom-nav-controlslayers, and pages of the deck’s own insetup/routes.ts(see Layers, controls and pages).- Export flags:
--format,--range,--dark,--with-clicks(also--with-clicks false),--omit-background,--timeout,--wait,--executable-path,--per-slide,--output.
Spelled differently
Section titled “Spelled differently”MDX is stricter than Slidev’s Markdown: attributes are JSX (className, style={{ color: "red" }},
numbers and arrays in braces), components start with a capital letter, every tag closes, and { and
< in prose need backticks or escaping.
| Slidev | Kadal Deck |
|---|---|
<div v-click>…</div>, <v-click> |
<Click>…</Click> |
v-click="3", v-click="'+2'", v-click="[2, 4]" |
<Click at={3}>, <Click at="+2">, <Click at={[2, 4]}> |
v-click.hide |
<Click hide> |
v-after, <v-after> |
<After> |
<v-clicks depth="2" every="2"> |
<Clicks depth={2} every={2}> |
v-switch with #1, #2-4 slots |
<Switch> with <Case at={1}>, <Case at="2-4"> |
v-mark.circle.red |
<Mark type="circle" color="red"> |
v-motion :initial :enter :click-1 |
<Motion initial enter clicks={{ "click-1": … }}> |
{{ $slidev.nav.currentPage }}, $page |
<SlideCurrentNo />; in a component, useSlide() |
{{ $clicks }} |
<ClickCount />; in a component, useClicks() |
colorSchema: dark |
colorScheme: dark |
transition: slide-left |
transition: slide (also fade, zoom, convex, concave, your own) |
components/*.vue |
React components in src/components.tsx, or a Vue island with @kadal/deck-vue |
layouts/*.vue |
"layout:name": Component in src/components.tsx |
global-top.vue, global-bottom.vue, custom-nav-controls.vue |
global-top.tsx, global-bottom.tsx, custom-nav-controls.tsx |
setup/shortcuts.ts |
<Deck shortcuts> or keyboard: in the headmatter |
setup/context-menu.ts |
<Deck contextMenu> |
setup/code-runners.ts |
the same file, with defineCodeRunners from @kadal/deck |
setup/routes.ts |
the same file (or routes.tsx), with defineRoutes from @kadal/deck |
onSlideEnter, onSlideLeave |
the same names, or useSlideEnter, useSlideLeave |
drawings: { persist: true } (files in .slidev/drawings) |
drawings: { persist: files } (files in drawings/; Slidev’s SVGs load) |
| UnoCSS classes | Tailwind v4 in the starter: most utility names are the same |
slidev, slidev build, slidev export, slidev format |
kadal-deck dev, kadal-deck build, kadal-deck export, kadal-deck format |
Slidev’s components
Section titled “Slidev’s components”| Slidev | Kadal Deck |
|---|---|
<Arrow>, <Link>, <Toc>, <Transform>, <Tweet>, <Youtube> |
the same names |
<AutoFitText> |
<FitText> |
<VClick>, <VAfter>, <VClicks> |
<Click>, <After>, <Clicks>; see Clicks and animation |
<VSwitch> |
<Switch> with <Case>s |
v-mark |
<Mark> |
<VDrag>, <VDragArrow>, the v-drag attribute |
<Drag>, <DragArrow>; see Components |
<SlidevVideo> |
<Video> |
v-motion |
<Motion> |
<LightOrDark>, <RenderWhen>, <SlideCurrentNo>, <SlidesTotal>, <TitleRenderer> |
the same names |
<carbon-logo-github /> and other icon tags |
the same tags, or <Icon name="carbon:logo-github" />; see Icons |
<PoweredBySlidev> |
the host’s “Made with Kadal Deck” badge |
In Comark syntax Slidev’s kebab-case names work too: :auto-fit-text[Big] is <FitText>.
Slidev features and their Kadal equivalents
Section titled “Slidev features and their Kadal equivalents”| Slidev feature | In Kadal Deck |
|---|---|
| Presenter mode, timer, notes editing | Presenter view, with pacing and a phone remote |
Quick overview, /overview |
O, and /overview with step sliders and notes |
| Drawing (drauu) | Drawing: pen, highlighter, shapes, eraser, undo |
| Camera view and recording | Camera and recording |
--remote, --tunnel |
kadal-deck dev --remote <password>, --tunnel; a hosted room with publish |
| Export to PDF, PNG, PPTX, Markdown | Export, plus /export in the browser and an editable PPTX |
build --download, download: |
the same; on deck.kadal.cc a paid-plan feature |
build for a static host, PWA, remoteAssets |
Build and host anywhere |
| Icons (Iconify) | Icons |
Theme packages, theme eject |
Themes, Writing a theme |
slidev format, Prettier plugin |
kadal-deck format, @kadal/prettier-plugin |
| Side editor, VS Code extension | E under kadal-deck dev; the VS Code extension |
| Hosting a static build | kadal-deck build, or kadal-deck publish to deck.kadal.cc |
Other spellings it accepts
Section titled “Other spellings it accepts”Decks that started life in reveal.js keep working too. These are synonyms, so you can paste them in and tidy up later:
<Fragment>is<Click>;<Stack>,<Stretch>and<Frame>are reveal.js’sr-stack,r-stretchandr-frame.- A code fence’s
data-line-numbers="1|2-3"(withdata-ln-start-from) means line numbers and highlight steps. autoslideon a slide isautoSlide.- The slide keys
backgroundColor,backgroundGradient,backgroundImage,backgroundSize,backgroundPosition,backgroundRepeat,backgroundOpacity,backgroundVideo,backgroundVideoLoop,backgroundVideoMuted,backgroundIframeandbackgroundInteractiveset the slide’s background. - Deck options keep reveal.js’s names:
controls,progress,slideNumber,hash,history,loop,rtl,navigationMode,autoSlide,viewDistanceand the rest of Configuration.
What Kadal adds
Section titled “What Kadal adds”A live room where the audience follows, reacts, asks and votes from their
phones; a phone remote with an air pointer, on a static host too; vertical
slides; auto-animate; 36 animated backgrounds; a theme menu on every screen; Excalidraw sketches
drawn in code; glossary term cards; narration; an editable
PowerPoint export; framework islands for Vue, Svelte, Solid and more; kadal-deck check; and
MCP servers for AI agents.