โœ…โ€‚Verifying a deck

How do I know my deck is actually correct?

Narroโ€™s characteristic failure is a green build that renders wrong. Every release so far has shipped at least one: a reveal that compiled and revealed nothing, a class that landed on no element, an export that reported success over an empty directory, a production build that rendered a black rectangle with four separate checks green over it.

So the honest version of โ€œrun narro checkโ€ is this page: what each gate sees, what it does not, and which one to run before you hand the deck to anyone.

The short answer

narro check deck.md --strict --fit     # markdown mode
npm run build && narro check dist --fit   # React mode

--strict catches names and syntax. --fit is the only one that opens a browser, and therefore the only one that can tell you the deck renders at all. Run both. Neither is slow enough to skip.

What each gate sees

GateSeesDoes not see
narro checkLayout, theme, slot and template names; markdown that reached the slide as literal text; a class or a .step that landed on nothing; a preamble import that resolves nowhereWhether the content fits; whether the page renders
narro check --renderThe compiled element tree, with the classes that actually landed on each element, and every component with its propsSize, colour, position โ€” it has no browser; and what a component renders, since it is not run
narro check --render-fullThe same, without shortening long text or large props to fit a terminal lineThe same
narro check --strictThe above, with warnings promoted to errors. The CI gateThe same blind spots
narro check --fitOverflow against the 1920ร—1080 canvas, in a real browser. Errors the page threw while loading, and whether it rendered any slides at allAnything below the tolerance; whether the content is right
narro build / vite buildType errors, unresolved imports, bundling failuresWhether the result renders โ€” which is why --fit on the built directory matters
narro export --pngThe deck as an audience sees it, one file per slide, every fragment revealedNothing else checks this; it is a good last look
narro capture-websiteOne slide, from a running preview, as a picture โ€” the fastest way to answer โ€œit passed every gate and still looks wrongโ€Only the slide you asked for, and only if you name it correctly โ€” see below

--fit is not part of --strict

They answer different questions and only one of them needs a browser, so they are separate flags. That means a CI job running --strict alone ships clipped slides โ€” one authorโ€™s deck had two of ten slides overflowing while --strict reported it clean.

If your CI image has Chromium, run both.

npx playwright install chromium   # or point NARRO_CHROMIUM at one you have

Which script CI should call

Every template ships two, and the difference is only what a missing browser means:

ScriptFit checkFor
npm run build--fit-if-available โ€” warns when there is no browserAuthoring. You want the dist/ even on a machine that cannot measure it
npm run verify--fit โ€” fails when there is no browserCI. A step that passes having measured nothing is worse than no step

lint and typecheck cannot see a clipped slide: both are about names, and a markdown deckโ€™s typecheck is --strict. verify is the one to wire into a pipeline. A deck built from narro new has no scripts at all โ€” there, narro build deck.md runs the measurement itself.

โ€Nothing was measuredโ€ is a failure

If no browser is installed, --fit says --fit needs a browser and none is installed and exits non-zero. It has measured nothing, and an exit code is the only part of this a CI job reads โ€” a step that passes having measured zero slides is worse than no step, because it reports the thing you wanted to know without having asked it.

When that is genuinely fine โ€” you are on a laptop with no browser and you want the rest of check to run โ€” ask for it by name:

narro check deck.md --fit-if-available   # warns instead of failing

That is the only way to get a pass out of an unmeasured deck, which is the point: it is a decision you make rather than one you arrive at.

--fit measures what you point it at

narro check deck.md --fit compiles the deck itself. narro check dist --fit measures the artifact you are about to publish, whatever produced it โ€” markdown mode, React mode, or a bundler you wired up yourself.

Those are different questions, and the second is the one that matters before you ship. A deck can compile perfectly and still build to a page that throws: point --fit at dist/ and it will tell you, because a page that renders no slides is a failure there rather than a deck with nothing to overflow.

npm run build
narro check dist --fit

This is also the only narro-provided verification React mode has, and the reason check takes a directory at all.

Looking at one slide

When a slide passes every gate and still looks wrong, the next step is a picture of that slide. narro export --png gives you all of them; for a single slide while you are still editing, screenshot the preview:

narro build deck.md -o dist
narro preview --outDir dist --port 4599 &
narro capture-website 'http://localhost:4599/#/slide/3' -o slide3.png

The URL must be #/slide/3, not #/3. A hash that is not a route is not an error โ€” the deck loads and shows slide 1 โ€” so #/3 returns a screenshot of the wrong slide, silently, as many times as you ask for it. narro capture-website warns when the hash is not a route it recognises, but the warning is easy to scroll past when you are taking six of them. Slide numbers are 1-based, and a slide with an id answers to #/slide/<id> as well. See Navigation for the full routing rules.

Two things people expect to be blind spots and are not

  • Content inside a .step fragment is measured. A fragment is hidden visually and stays in layout, so --fit measures an unrevealed step exactly as it measures a revealed one โ€” a 3000px box reports the same overflow either way. --fit is not weaker on the slides that use fragments, which are usually the densest ones.
  • A size authored on a ::: container reaches the text inside it. It did not always: .rs-deck p styles the paragraph directly, and an ancestorโ€™s value can only arrive by inheritance, which a direct rule beats. :::{.text-[8rem]} around a paragraph left it at the body size while check, --render and --fit all passed. The compiler now marks containers that set a font size, line height, font family or letter spacing, and the base stylesheet stands down for them.

What no gate can see

Be clear-eyed about the edge of the box:

  • Whether the slide says the right thing. Nothing checks your argument.
  • Whether it reads at the back of the room. --fit knows the content is inside the canvas; it does not know 14px monospace is unreadable on a projector. Export a PNG and look at it at armโ€™s length.
  • Colour and contrast. Neither the compiler nor the fit checker has an opinion about a dark grey on a slightly darker grey.
  • Anything behind a Motion* componentโ€™s own timeline. Those run on their own clock rather than on the deckโ€™s steps.

The habit

While writing: narro check deck.md --render after each slide. It is about 90ms, it prints exactly which element each class landed on, and it is the fastest way to find out that an attribute block attached to something you did not mean. It shortens long lines so a ten-slide outline stays scannable; when the thing you are checking is a long line โ€” a forty-class utility string, a paragraph you need to read back โ€” --render-full prints it whole.

Before committing: narro check deck.md --strict --fit.

Before presenting or sending: narro export --png and look at the images. It is the only step that shows you what an audience sees, and it is the one that has caught the most.

IntroductionWhat is Narro, and should I be writing markdown or React?
InstallationWhat do I install, and what does the project look like afterwards?
First DeckWhat does a working React deck look like, end to end?
Markdown ModeHow do I split a slide, style one word, or reveal a line โ€” without leaving the markdown file?
Writing AI PromptsWhat do I tell an AI so the deck it writes actually builds?
Rules for AI AgentsWhat do I paste into my repo so an agent stops writing decks that build wrong?
React APIWhich component or hook do I import, and what does it take?
CLIWhich command do I run, and what are its flags?
Markdown APIHow do I read, edit, or validate a deck file from my own code instead of by hand?
Component ReferenceWhat props does this component take, and which package do I import it from?
Verifying a deckHow do I know my deck is actually correct?
AnimationHow do I reveal a list one line at a time, or move between slides with something other than a cut?
NavigationHow does the audience move through the deck, and how do I present it?
Canvas & PositioningHow do I put something at an exact position instead of in the flow?
Images & MediaHow do I use an image as a background, tint it, or embed a video?
ArchitectureWhich package owns what, and why is the seam where it is?
Transform ModeHow do I zoom and pan across one big canvas instead of cutting between slides?
Import & ExportHow do I get this deck out as PPTX, PDF, or one file I can email?
AI IntegrationHow do I wire an AI assistant up to Narro so it can write and build decks?
ThemingHow do I change the colours, fonts, and overall look of a deck?
Deck TemplatesHow do I define one house style with named layouts my slides can reference, like a PowerPoint master?
TroubleshootingSomething is wrong with my deck. What is it, and how do I fix it?
LimitationsWhat can't Narro do, and what do I do instead?