slidevjs/slidev · error · Error
Magic Move block must contain at least one code block
Error message
Magic Move block must contain at least one code block
What it means
Thrown by the magic-move codeblock transformer when a quad-fence (````) block whose info line matches the magic-move pattern contains zero inner triple-fence code blocks (RE_CODE_BLOCK finds no matches). Magic Move animates between successive code steps, so at least one step is mandatory.
Source
Thrown at packages/slidev/node/syntax/codeblock/magic-move.ts:55
const info = `${snippet.lang} ${snippet.meta}`.trim()
const content = snippet.content.endsWith('\n') ? snippet.content : `${snippet.content}\n`
return `\`\`\`${info}\n${content}\`\`\``
}).join('\n')
}
export default defineCodeblockTransformer(async ({ info, fence, code, slide, options: { userRoot, data: { config, watchFiles }, utils: { shikiOptions, shiki } } }) => {
if (fence !== 4)
return
const match = info.match(RE_MAGIC_MOVE_INFO)
if (!match)
return
const [, title = '', options = '{}'] = match
const defaultLineNumbers = parseLineNumbersOption(options) ?? config.lineNumbers
const resolvedCode = slide ? resolveMagicMoveSnippetImports(code, userRoot, slide, watchFiles) : code
const matches = Array.from(resolvedCode.matchAll(RE_CODE_BLOCK))
if (!matches.length)
throw new Error('Magic Move block must contain at least one code block')
const ranges = matches.map(i => normalizeRangeStr(i[3]))
const steps = await Promise.all(matches.map(async (i) => {
const lang = i[1]
const lineNumbers = parseLineNumbersOption(i[4]) ?? defaultLineNumbers
const code = i[6].trimEnd()
const options = {
...shikiOptions,
lang,
}
const { tokens, bg, fg, rootStyle, themeName } = await shiki.codeToTokens(code, options)
return {
...toKeyedTokens(code, tokens, JSON.stringify([lang, 'themes' in options ? options.themes : options.theme]), lineNumbers),
bg,
fg,
rootStyle,
themeName,
lang,View on GitHub (pinned to 0d798ace58)
Solutions
- Add at least one triple-backtick code block inside the magic-move container as a step.
- Ensure inner fences use exactly three backticks and the closing fence is at column 0, matching RE_CODE_BLOCK.
- If using <<< snippet imports inside magic-move, verify they resolve to non-empty fenced code.
- Provide multiple steps to actually see the animation; one step is the minimum but two-plus is the intent.
Example fix
// before: magic-move with no inner code block ````md magic-move Some prose only. ```` // after: at least one fenced step ````md magic-move ```js const a = 1 ``` ```js const a = 2 ``` ````
Defensive patterns
Strategy: validation
Validate before calling
const RE_CODE_BLOCK = /^```[\w'-]+[\s\S]*?^```$/gm
function hasInnerCodeBlock(body: string): boolean {
return RE_CODE_BLOCK.test(body)
}
// before publishing a deck, scan magic-move blocks:
if (!hasInnerCodeBlock(magicMoveBody)) {
throw new Error('magic-move block needs at least one fenced code step')
} Type guard
function isValidMagicMoveBody(body: string): boolean {
return /^```/m.test(body) && /\n```\s*$/m.test(body)
} Try / catch
try {
await renderSlide(md)
} catch (e) {
if (e instanceof Error && e.message === 'Magic Move block must contain at least one code block') {
return { error: 'Add at least one ``` fenced code step inside the magic-move block.' }
}
throw e
} Prevention
- Always include at least two ``` code steps inside a ````md magic-move block (one minimum, two for animation).
- Use exactly three backticks for inner fences and four for the outer container.
- Place inner closing fences at column 0 to match RE_CODE_BLOCK.
- Verify <<< snippet imports inside magic-move resolve to non-empty fenced code.
When it happens
Trigger: Writing a ````md magic-move ... ```` block whose body has no nested fenced code block - e.g. only prose, an empty body, or inner fences that don't match the strict RE_CODE_BLOCK pattern (wrong fence length, leading spaces).
Common situations: Forgetting to add the inner code step(s); using three backticks inside a four-backtick container without matching the regex; snippet imports that resolve to empty content; mismatched fence counts so the inner block isn't captured.
Related errors
- Invalid timestamp format
- Invalid timestamp value: ${match[1]}
- Invalid timestamp unit: ${unit}
- Unknown timestamp remaining: ${remaining}
- Invalid aspect ratio "${str}"
AI-assisted analysis of slidevjs/slidev@0d798ace58 (2026-08-12).
Data as JSON: /api/errors/558b59c517867577.
Report an issue: GitHub.