πŸš§β€‚Limitations

What can't Narro do, and what do I do instead?

Every entry here is something an author found by hitting it while writing a deck. Knowing the shape of the box up front is worth more than discovering an edge at the wrong moment, so this page is deliberately blunt.

If something is missing here and surprised you, that is a bug in this page.

Two restrictions that used to live here are gone, in case you have read an older copy: the attribute syntax now accepts every Tailwind class, including arbitrary values, opacity modifiers and variants; and Card, Badge and Callout exist, so a bordered panel or a pill is no longer hand-rolled.

There is no diagram primitive

Diagram draws ASCII art. FlowDiagram and ProcessFlowChart draw linear step chains. Nothing draws a nested tree, a mapping between two structures, or boxes joined by labelled connectors β€” the shapes a technical deck asks for most.

This is the largest thing Narro does not have. It is not a small gap and this page will not pretend otherwise.

Instead: build it out of containers and Tailwind. Every author who has hit this reinvented the same thing, so here it is once, ready to paste. Nested ::: containers give the boxes; twelve lines of deck.css give the connectors.

/* deck.css β€” a row of boxes joined by arrows. */
.rs-flow {
  display: flex;
  align-items: stretch;
  gap: 3rem;
}
.rs-flow > .rs-container {
  position: relative;
  display: flex;
  align-items: center;
}
/* The line, and then the head, both centred on the gap before each box after
   the first. `top: 50%` is load-bearing: without it an absolutely positioned
   pseudo-element falls to its static position, and the arrowhead lands under
   the line instead of on it. */
.rs-flow > .rs-container + .rs-container::before {
  content: "";
  position: absolute;
  top: 50%;
  right: 100%;
  width: 3rem;
  height: 2px;
  transform: translateY(-50%);
  background: currentColor;
  opacity: 0.35;
}
.rs-flow > .rs-container + .rs-container::after {
  content: "";
  position: absolute;
  top: 50%;
  right: calc(100% - 1px);
  transform: translateY(-50%);
  border: 7px solid transparent;
  border-left-color: currentColor;
  opacity: 0.35;
}
:::{.rs-flow}

:::{.rounded-xl .border .border-white/20 .p-6}

**Deck**

:::

:::{.rounded-xl .border .border-white/20 .p-6}

**Compile**

:::

:::{.rounded-xl .border .border-white/20 .p-6}

**Slides**

:::

:::

For a tree rather than a row, nest the containers and swap flex for flex-col, using ml-8 border-l on the children to draw the trunk. For anything where the connectors cross β€” a mapping between two columns, a cascade that skips a level β€” the honest answer is an SVG: draw it in whatever you draw diagrams in, and use ![](./diagram.svg){.w-full}. Nothing in Narro will do it for you.

Chart is deliberately small

Bar, line, pie and donut, drawn with CSS and SVG. Bars can be horizontal or vertical, can print their values, and can carry a unit or a valueFormat; there is no axis, no gridline, no tooltip, no time series, and no stacking. A bar chart therefore has no scale reference β€” the bars are proportional to each other and to nothing else.

Instead: for anything analytical, render it with a real charting library and put the result in a slide. Chart is for the one number a slide is about.

Markdown mode cannot loop

There is no iteration, no reuse, and no reference-and-repeat. Twelve cards is twelve containers: a twelve-item grid is around 120 lines of near-identical markdown, and a change to the shape is twelve edits. Nothing about it is blocked β€” it is just long, and it is the sharpest edge of the mode.

Instead: past about six repeated items, MDX and a .map() is 15 lines and one place to edit. One import in the deck preamble buys it; the deck stays a markdown file.

check --fit needs a browser

Every other check reads the compiled deck and needs nothing. --fit measures real layout, so it needs Chromium β€” npx playwright install chromium, or point NARRO_CHROMIUM at one your image already has. Without a browser it reports that nothing was measured and, under --strict, fails; it never reports a pass it did not verify.

There is no way to know a slide fits without rendering it, so there is no browser-free version of this check to build.

Markdown mode has no per-slide state

A slide cannot hold a value that changes as the audience watches β€” a selected swatch, a filtered table, a live counter. {.step} reveals content that was always there; it does not compute anything.

Instead: write a component and mount it. In a markdown deck, import it in the preamble; the component is ordinary React and can hold whatever state it likes.

Reveal order is per slide, not per deck

{.step} and <Fragment> reveal in source order within their slide, and every slide starts again from nothing. There is no deck-wide sequence, and no way to carry a revealed state across a slide boundary.

Two animation systems

CSS-keyframe components live in @getnarro/shared-ui (Fragment, Animation, AnimatedList, WordReveal); Framer Motion components live in @getnarro/core (Motion, MotionText, MotionNumber, MotionSteps). They overlap β€” CountingNumber and MotionNumber both count, TypewriterText and MotionText type="typewriter" both type β€” and their durations use different units: milliseconds in shared-ui, seconds in core.

Which to use: the Motion* components when you want physics, spring easing or layout animation; the shared-ui ones for a simple reveal. If you are choosing between two components with similar names, they do the same thing and either is fine.

Mind the unit when you move a number between them. <Fragment duration={300}> is 300ms; <Motion duration={300}> is five minutes. The overlap is a deferred decision and the unit mismatch is the sharp edge on it β€” check which package the component came from before copying a duration across.

No live data

A deck is a static build. Nothing polls, subscribes, or refetches on its own.

Instead: fetch in a component with useEffect like any React app, or bake the number in at build time β€” which is usually what a deck wants, since a presenter needs to know what the slide says before they say it.

Export needs a browser

narro export --pdf and --png drive Playwright. In CI that means installing it:

npm install -D playwright && npx playwright install chromium

Without a browser: narro check deck.md --render prints what each slide renders β€” elements, classes and text β€” which covers β€œdid that class land where I meant” without a screenshot. It cannot tell you whether the slide fits; only --fit can, and that needs the browser too.

Code has no syntax highlighting

Code renders a styled block with the language recorded as a class and no tokenising. For a deck where four slides carry code, that is a visible ceiling β€” the clean example and the ugly one look equally grey.

Instead: highlight it yourself and drop the result in β€” Shiki’s codeToHtml at build time, or any highlighter that keys off the language-* class the fence already carries.

The runtime brings Framer Motion

@getnarro/core depends on framer-motion, and it ships whether or not a deck uses a Motion* component. Measured: a three-slide markdown deck with no components at all is 114 KB gzipped, and adding a <Motion> to it costs 1 KB β€” which is the proof that the library was already there. A ten-slide deck using a few shared-ui components lands around 130 KB. Almost none of that is your content, and there is no lighter entry point.

If bundle size is the point of your deck: narro bundle produces one self-contained HTML file, narro export --png produces images, and neither carries a runtime at all.

The npm name narro is someone else’s

npx narro downloads an unrelated package. The CLI is @getnarro/cli, and every Narro package is under the @getnarro/ scope. There is no fix for this beyond using the scoped name.

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?
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?