# Narro > Developer-first presentations. Author decks in Markdown or React, render them > in the browser, export to PPTX, PDF, or a single HTML file. Narro is text, so you can write a deck directly. This file plus the linked resources are enough — you do not need to have seen the repository. ## Read this first - **Everything in ONE file (no follow-up fetches — take this if you can only fetch once):** https://getnarro.com/llms-full.txt - **No web access?** The same documentation ships through npm, over the registry that already installed your packages: `npm i -D @getnarro/docs`, then read `@getnarro/docs/llms-full.txt`, `@getnarro/docs/catalog.json`, or `@getnarro/docs/content/.md`. It pins to the version actually installed. - **Rules to paste into your own AGENTS.md / CLAUDE.md / .cursor/rules:** https://getnarro.com/docs/agent-rules.md - **Then check your work** — this is the part that matters more than the prose: npx @getnarro/cli check deck.md --render # what every slide renders: elements, classes, text npx @getnarro/cli check deck.md --strict # the CI gate: warnings are errors npx @getnarro/cli check deck.md --fit # does the content fit the slide? (needs a browser) npx @getnarro/cli build deck.md # the real build `check` names the bad layout, the unknown theme, the malformed directive, and every class that was written but landed on nothing. A deck you have not run it on is a deck you have not verified — and `--strict` alone does not check that the deck *fits*, or that the built page renders at all; see https://getnarro.com/docs/verifying.md for what each gate does and does not see. `--strict` is the CI gate; `--render` answers "did that class attach to the word or the paragraph?" without a browser; `--fit` is the only check that can tell you a dense slide is clipping, and the only narro-provided check React mode has (point it at `dist/`). ## Fastest path to a deck npx @getnarro/cli new deck.md # a single-file markdown deck npx @getnarro/cli dev deck.md # open it, hot-reloading If the deck has to stay one file — no companion `deck.css`, no components, no install — that is **single-file mode**, and https://getnarro.com/docs/markdown-mode.md#single-file-mode says what it can and cannot express. Iteration, per-slide state, custom CSS and syntax highlighting are the things it rules out; everything about layout, containers, slots, fragments and speaker notes works. Or scaffold a React project: npm create narro@latest my-deck Note: the npm package named `narro` is unrelated to this project. Narro's packages are all under the `@getnarro/` scope, and the CLI binary `narro` comes from `@getnarro/cli`. ## Which mode | You want | Use | Start at | | --- | --- | --- | | One file, no build setup, deck is prose-shaped | Markdown mode | https://getnarro.com/docs/markdown-mode.md | | Live components, data, custom interaction | React mode | https://getnarro.com/docs/react-api.md | You do not have to choose permanently: markdown decks climb to per-element Tailwind, then to ejected React layouts, then to MDX, without being rewritten. ## Closed sets — do not invent names Every value below is generated from the shipping source. If a name is not in this list it does not exist, and a deck naming it will build and render wrong rather than fail loudly. The full machine-readable set, including every component's props and every exported string union, is one fetch: - https://getnarro.com/catalog.json - https://getnarro.com/components.json — every component's props, read out of the TypeScript types - https://getnarro.com/llms/.md — one component's reference, on its own URL (names are case-sensitive and exact: https://getnarro.com/llms/Presentation.md). https://getnarro.com/llms/index.md lists every valid name; a guessed name 404s, so look it up rather than retrying. - https://getnarro.com/schema/deck-frontmatter.schema.json - https://getnarro.com/schema/slide-frontmatter.schema.json Built-in layouts (`layout:` in slide frontmatter): default, cover, section, quote, end, two-column, three-column, image-right, image-full Transitions (`transition:` in deck or slide frontmatter): none, fade, slide, zoom `TransitionType` in `@getnarro/shared-ui` is a larger, *different* set belonging to the standalone `SlideTransition` component. Frontmatter does not accept it. Themes (`theme:` in deck frontmatter): default, corporate-dark, minimalist-light, creative-gradient, ocean-blue, forest-green, sunset-warm, midnight-purple, tech-dark, startup-bold, academic-classic Colour schemes — **not a frontmatter key.** They are palettes, applied with `narro themes apply --color-scheme` or read as data from `@getnarro/marketplace` (`getColorSchemeById`): ocean-blue, sunset-warm, forest-mist, royal-purple, rose-gold, arctic-blue, charcoal-slate, emerald-night Font presets — **not a frontmatter key.** Applied from React with `applyFontPreset(name)` from `@getnarro/core`: system, inter, roboto, openSans, lato, montserrat, poppins, raleway, sourceSerif, playfairDisplay, merriweather, jetBrainsMono Deck frontmatter keys: title, author, date, theme, template, aspectRatio, transition, class, keyboard, mouse, touch, routing, favicon, maxDuration, plus any key beginning `x-` — those are yours, nothing reads them, and `check --strict` stays quiet. Slide frontmatter keys: layout, class, id, transition, align, background, notes Tailwind in `{ … }`: whatever Tailwind calls it, including arbitrary values (`text-[10px]`, `bg-[#0a0a0a]`), opacity modifiers and fractions (`bg-white/5`, `w-1/2`), variants (`hover:`, `md:`, `dark:`), half-steps (`p-1.5`) and `!` overrides. The one exception is a literal `{` or `}` inside the block — put that class in `deck.css`. Templates for `npm create narro@latest`: markdown-minimal, markdown-app, markdown-docs, minimal, corporate, developer, creative, bold-statement, visual-story, data-narrative, startup-pitch, tech-conference, template0 Components (81 total; props at https://getnarro.com/components.json): AnimatePresence, Canvas, CanvasElement, CanvasShape, ErrorBoundary, ErrorOverlay, MarqueeRow, MasonryBackground, Motion, MotionContainer, MotionList, MotionNumber, MotionPresence, MotionSpotlight, MotionStep, MotionSteps, MotionText, MotionTransform, NebulaBackground, Notes, OverviewGrid, Presentation, PresenterNotes, Slide, SlideContent, TransformSlide, TrustedByMarquee, AgendaItem, AnimatedList, Animation, Badge, BigText, Callout, Card, Chart, Code, CommentForm, CommentItem, CommentMarker, CommentPanel, ContentSlide, CountingNumber, Diagram, DottedArc, DualColumnTextAndImage, Embed, FlowDiagram, FourColumn, Fragment, Grid, Heading, HexPatternBackground, Icon, Image, ImageBackground, ImageSlide, ImageSpotlight, InfoCardWithIcon, List, LogoGrid, MorphElement, ProcessFlowChart, ProfileCard, ProgressRing, Quote, Reveal, ScrollableImage, Text, ThreeColumn, TimelineWithIndicators, TitleSlide, TitleSlideWithElement, TwoColumn, TwoColumnHighlight, TypewriterText, Video, WaveLine, WordReveal, Workflow, WorkflowStep, ZoomImage ## Documentation Every page below is also available as markdown by appending `.md` to its URL — same content, a fraction of the tokens, no markup to parse back out. ### Start here - [Introduction](https://getnarro.com/docs/introduction.md): What is Narro, and should I be writing markdown or React? - [Installation](https://getnarro.com/docs/installation.md): What do I install, and what does the project look like afterwards? - [First Deck](https://getnarro.com/docs/first-deck.md): What does a working React deck look like, end to end? - [Markdown Mode](https://getnarro.com/docs/markdown-mode.md): How do I split a slide, style one word, or reveal a line — without leaving the markdown file? - [Writing AI Prompts](https://getnarro.com/docs/ai-prompts.md): What do I tell an AI so the deck it writes actually builds? - [Rules for AI Agents](https://getnarro.com/docs/agent-rules.md): What do I paste into my repo so an agent stops writing decks that build wrong? ### Reference - [React API](https://getnarro.com/docs/react-api.md): Which component or hook do I import, and what does it take? - [CLI](https://getnarro.com/docs/cli.md): Which command do I run, and what are its flags? - [Markdown API](https://getnarro.com/docs/markdown-api.md): How do I read, edit, or validate a deck file from my own code instead of by hand? - [Component Reference](https://getnarro.com/docs/components.md): What props does this component take, and which package do I import it from? ### Guides - [Verifying a deck](https://getnarro.com/docs/verifying.md): How do I know my deck is actually correct? - [Animation](https://getnarro.com/docs/animation.md): How do I reveal a list one line at a time, or move between slides with something other than a cut? - [Navigation](https://getnarro.com/docs/navigation.md): How does the audience move through the deck, and how do I present it? - [Canvas & Positioning](https://getnarro.com/docs/canvas-positioning.md): How do I put something at an exact position instead of in the flow? - [Images & Media](https://getnarro.com/docs/media.md): How do I use an image as a background, tint it, or embed a video? - [Architecture](https://getnarro.com/docs/architecture.md): Which package owns what, and why is the seam where it is? - [Transform Mode](https://getnarro.com/docs/transform-mode.md): How do I zoom and pan across one big canvas instead of cutting between slides? - [Import & Export](https://getnarro.com/docs/import-export.md): How do I get this deck out as PPTX, PDF, or one file I can email? - [AI Integration](https://getnarro.com/docs/ai-integration.md): How do I wire an AI assistant up to Narro so it can write and build decks? - [Theming](https://getnarro.com/docs/theming.md): How do I change the colours, fonts, and overall look of a deck? - [Deck Templates](https://getnarro.com/docs/deck-templates.md): How do I define one house style with named layouts my slides can reference, like a PowerPoint master? - [Troubleshooting](https://getnarro.com/docs/troubleshooting.md): Something is wrong with my deck. What is it, and how do I fix it? - [Limitations](https://getnarro.com/docs/limitations.md): What can't Narro do, and what do I do instead? --- Everything above is https://getnarro.com/llms.txt. Below is every documentation page in full, in reading order. Component props in the API reference are generated from the TypeScript source, so they describe the shipping API rather than a hand-maintained copy of it. --- # Introduction > What Narro is, and which of its two authoring modes you want > > Read this when: What is Narro, and should I be writing markdown or React? Narro builds presentations out of web technology. A deck is either a markdown file or a tree of React components; either way it renders in a browser, lives in Git, and exports to PPTX, PDF, or a single self-contained HTML file. ## Two ways to write a deck Both modes use the same runtime, the same navigation, and the same export pipeline. Pick by how much control you want, not by how big the deck is — markdown decks can drop into React wherever they need to. ### Markdown mode One `.md` file, no project to scaffold: ```bash npx @getnarro/cli new deck.md npx @getnarro/cli dev deck.md ``` The dialect is designed as a **ladder with no cliff**: plain markdown → layout directives → per-element Tailwind → your own React layouts → full MDX with live components. You climb it without ever rewriting the deck. See [Markdown mode](/docs/markdown-mode). ### React mode A Vite project where every slide is a component: ```bash npm create narro@latest my-deck ``` Reach for this when slides need real application logic — live data, custom interaction, a component you already have. See [First deck](/docs/first-deck). ## Why not PowerPoint Narro is worth the setup when you need something a WYSIWYG tool cannot give you: - **Code that is really code** — a fence in your deck is the same text your editor lints, syntax highlighted at build time, and live components if you want them running on the slide - **Version control** — a deck is text, so it diffs and merges - **Reuse** — a slide is a component; a component works in the next deck too - **Generation** — text in, deck out, which is what makes AI authoring practical If your deck is five bullet points and a photo, use PowerPoint. Narro is for the cases where you have been fighting the tool. ## Packages | Package | What it is | | --- | --- | | `@getnarro/cli` | CLI tools for creating and managing Narro presentations | | `@getnarro/core` | The React runtime for Narro presentations — Presentation, Slide, navigation, transitions, and speaker notes | | `@getnarro/docs` | Narro's documentation as data — markdown pages, a generated component API reference, and llms.txt | | `@getnarro/markdown` | Markdown & MDX authoring for Narro presentations | | `@getnarro/marketplace` | Themes, colour schemes, and templates for Narro presentations | | `@getnarro/mcp-server` | MCP (Model Context Protocol) server for AI-assisted Narro presentation creation | | `@getnarro/shared-ui` | Slide components for Narro presentations — headings, text, lists, code, media, charts, and layouts | | `create-narro` | Scaffold a new Narro presentation | > The npm package named `narro` is unrelated to this project. Everything here is > under the `@getnarro/` scope. The binary called `narro` comes from > `@getnarro/cli`. --- # Installation > Get a deck running, in either mode > > Read this when: What do I install, and what does the project look like afterwards? Node.js 20 or newer. Any of npm, pnpm, yarn, or bun. ## Markdown mode: no install ```bash npx @getnarro/cli new deck.md # write a starter deck npx @getnarro/cli dev deck.md # serve it with hot reload npx @getnarro/cli build deck.md # static build into dist/ ``` Nothing is scaffolded and no config file is created. The CLI brings its own runtime, so a lone `.md` file in an empty directory is a complete deck. `markdown-minimal` scaffolds the same thing with a `package.json` that pins the CLI version and gives you scripts: ```bash npm create narro@latest my-deck -- --template markdown-minimal ``` For a deck that needs its own dependencies, pinned versions, or React layouts, `markdown-app` puts the same `deck.md` inside a Vite project: ```bash npm create narro@latest my-deck -- --template markdown-app ``` ## React mode: scaffold a project ```bash npm create narro@latest my-deck cd my-deck npm install npm run dev ``` `npm create narro` prompts for a template. To skip the prompt: ```bash npm create narro@latest my-deck -- --template developer ``` ### Templates **Markdown** | Template | What it is | Notes | | --- | --- | --- | | `markdown-minimal` | One `deck.md` and a pinned CLI. Nothing to build. | **start here** | | `markdown-app` | The same deck inside a Vite project, for React components and custom CSS. | — | | `markdown-docs` | Documentation-shaped deck: long code samples, wide tables. | ships an example deck | **React** | Template | What it is | Notes | | --- | --- | --- | | `minimal` | Three slides and nothing else. Start here to write your own. | **start here** | | `corporate` | Restrained business deck, dark theme. | ships an example deck | | `developer` | Code-heavy technical talk with terminal chrome. | ships an example deck | | `creative` | Bold colour and large type. | ships an example deck | | `bold-statement` | One idea per slide, very large type. | ships an example deck | | `visual-story` | Full-bleed imagery with overlay text. | ships an example deck | | `data-narrative` | Charts and figures, light theme. | ships an example deck | | `startup-pitch` | Ten-slide investor pitch, filled in. | ships an example deck | | `tech-conference` | Conference talk with a title card and code slides. | ships an example deck | | `template0` | Reference deck exercising every component. Read it, do not start from it. | ships an example deck | Pass `--mode markdown` to be prompted with only the markdown templates, or `npx create-narro --list` to print the table above from the version you have installed. **Scripting it.** `--yes` takes the default for anything you did not pass and never opens a prompt, which is what a CI job or an agent needs: ```bash npx create-narro my-deck --yes --skip-install # minimal, React npx create-narro my-deck --yes --mode markdown --skip-install # markdown-minimal npx create-narro my-deck --yes --template markdown-app --skip-install ``` A `--template` that is not on the list above is an error naming the ones that are — it is never quietly replaced with a default. ## Adding Narro to an existing app ```bash npm install @getnarro/core @getnarro/shared-ui ``` `react` and `react-dom` 19+ are peer dependencies. Styling assumes Tailwind CSS v4; add `@tailwindcss/vite` to your Vite config if you do not have it. ## What a scaffolded project looks like ```bash my-deck/ ├── index.html ├── package.json ├── vite.config.ts ├── tsconfig.json └── src/ ├── App.tsx # the deck — this is the file you edit ├── main.tsx # mounts App └── index.css # Tailwind entry ``` A markdown template swaps `src/App.tsx` for a `deck.md` at the project root and a `src/main.tsx` that mounts the compiled deck. ## Scripts | Command | What it does | | --- | --- | | `npm run dev` | Dev server with hot reload, on | | `npm run build` | Production build into `dist/` | | `npm run preview` | Serve the built deck | | `npm run fit` | Measure the built deck in a browser and report slides that clip | | `npm run lint` | The CI gate — `narro check --strict` for a markdown deck, Prettier for a React one | ## Which versions this reference describes The `@getnarro/*` packages version independently: a fix to the CLI does not move `core`, and `@getnarro/docs` has its own release line. So the reference you are reading can describe a runtime newer than the one your project installed, and nothing about that failure is loud — a prop that does not exist yet is simply ignored. Two things make it visible: - `narro check` prints the version of the CLI, the dialect, `core` and `shared-ui` that actually answered. That is the runtime you have. - This copy of the documentation was generated against: | Package | Version this reference was generated against | | --- | --- | | `@getnarro/cli` | 0.8.0 | | `@getnarro/core` | 0.6.0 | | `@getnarro/docs` | 0.10.0 | | `@getnarro/markdown` | 0.9.0 | | `@getnarro/marketplace` | 0.2.0 | | `@getnarro/mcp-server` | 0.3.10 | | `@getnarro/shared-ui` | 0.6.0 | | `create-narro` | 0.8.0 | If those disagree and something documented is missing, the runtime is older than the reference. `npm i -D @getnarro/docs` installs the documentation over the same registry that installed your packages, so it pins alongside them. --- # First Deck > Build a three-slide React deck end to end > > Read this when: What does a working React deck look like, end to end? This walks through a React deck. If you want the markdown path instead, see [Markdown mode](/docs/markdown-mode) — it is shorter. ## 1. Scaffold ```bash npm create narro@latest my-first-deck -- --template minimal cd my-first-deck npm install ``` ## 2. Write the slides Open `src/App.tsx`. Note where each component comes from: the structural pieces are in `@getnarro/core`, the content pieces in `@getnarro/shared-ui`. ```tsx import { Notes, Presentation, Slide, SlideContent } from "@getnarro/core"; import { Heading, List, Text } from "@getnarro/shared-ui"; export function App() { return ( Welcome to Narro Build presentations with React Introduce yourself and set up the problem. Why Narro? Spend the most time on the export story. Questions? ); } ``` ## 3. Run it ```bash npm run dev ``` Open . Arrow keys or space move between slides; `F` is fullscreen, `O` is the overview grid. Because `id` is set on each slide, the URL tracks position — `#/slide/features` links straight to the second slide. ## 4. Build ```bash npm run build # → dist/ npm run preview # serve it ``` `dist/` is a static site. Any host will serve it. ## Things worth knowing early - **`` never renders on the slide.** It feeds the presenter view. - **`SlideContent` handles vertical rhythm.** Its `layout` prop is "default" | "centered" | "top" | "bottom" | "fill" | "between" — a different set from `Slide`'s own `layout` prop, which is "default" | "center" | "two-column" | "image" | "title". - **Tailwind classes work everywhere.** `className` on any Narro component is merged, not replaced. ## Next - [Markdown mode](/docs/markdown-mode) — the same deck in one `.md` file - [Component reference](/docs/components) — everything you can import - [Animation](/docs/animation) — fragments and progressive reveals --- # Markdown Mode > The full markdown and MDX dialect — slides, layouts, Tailwind attributes, fragments > > Read this when: How do I split a slide, style one word, or reveal a line — without leaving the markdown file? Write a deck as one markdown file, then climb into React only where you need it. This page is the complete dialect reference. ```bash npx @getnarro/cli new deck.md # starter deck npx @getnarro/cli dev deck.md # hot-reloading server npx @getnarro/cli check deck.md # validate without building npx @getnarro/cli build deck.md # static build ``` `check` is the fast loop. It resolves every layout, slot, theme and frontmatter name and reports the ones that do not exist, along with what would have worked — then compiles each slide and reports what only a compiled slide can show: - markdown that reached the slide as **literal text** — an emphasis run that never closed, an attribute block that attached to nothing, a `::slot::` marker written mid-line. This is the failure that used to reach an audience, because the deck built cleanly around it. - a slide that renders **blank** because a stray `---` created it - a `{` MDX read as JavaScript, or an HTML tag that needs closing Compiling costs about 90ms for ten slides, so it stays a loop you can run after every edit. `narro build` applies the same rules, so a deck that renders its own source does not build. ## Seeing what a slide renders `narro check deck.md --render` prints the elements, classes and text of every slide. No browser, no screenshot — it reads the compiled slide: ``` Slide 1 layout: cover id: hook h1.text-7xl strong.text-red-500 Important! text works now p text Two code.bg-red-500 a text and code.bg-blue-500 b pre.rounded-xl.bg-black code.language-bash npx @getnarro/cli dev deck.md notes: phase 1 ``` This is the fastest way to answer "did that class land on the word or the paragraph?" — the question the attribute syntax raises most often, and the one a build cannot answer. ## The customization ladder | Rung | You write | Lives in | | --- | --- | --- | | 1. Plain markdown | `# Heading`, lists, tables | the `.md` file | | 2. Directives | `layout:`, `class:`, `theme:` in frontmatter | the `.md` file | | 3. Tailwind utilities | `{.text-8xl .text-blue-400}` on any element | the `.md` file | | 4. Ejected layouts | a React component in `layouts/` | beside the file | | 5. Full MDX | `import` + `` | file + components | Nothing is rewritten as you move up a rung. ### Single-file mode Rungs 1–3 live entirely in the `.md`, and that is a mode worth naming because some decks have to be one file: a deck pasted into an issue, committed next to the code it documents, or handed to someone who will not run an install. `npx @getnarro/cli new deck.md` produces exactly that file and nothing else, and `npx @getnarro/cli build deck.md` builds it cold in an empty directory — no `package.json`, no `node_modules`, no config. Everything up to rung 3 is available: layouts, slots, headers and footers, nested containers, grids, code panels, tables, colour, typography, fragments, and speaker notes. What is not, and what to do instead: | Not available | Because | Instead | | --- | --- | --- | | Iteration | The dialect cannot loop | Repeat the block, or leave the mode at about a dozen items | | Per-slide state | No React | Show the states side by side | | Custom CSS | `deck.css` is a companion file | Tailwind utilities only. The two things authors reached for `deck.css` to fix both work here: vertical alignment is the `align:` slide key, and theme-following colour is `bg-accent` and friends (below) | | `typecheck` | There is no TypeScript in one `.md` | `narro check deck.md --strict` is the equivalent gate | **Colour still follows the theme.** Every theme's colours are Tailwind theme tokens, so `bg-accent`, `bg-background`, `bg-foreground`, `bg-muted`, `bg-primary`, `bg-secondary` — and the matching `text-*` and `border-*` — are ordinary attribute-syntax classes that move when `theme:` moves. A palette colour like `{.bg-cyan-400}` does not, which is right when the colour is the point and wrong when it is a role. See [Theming](/docs/theming#colours-that-follow-the-theme). If you need a version-pinned deck that is still one file, `markdown-minimal` is the same deck plus a `package.json`. ## Slides and frontmatter Slides are separated by a line of exactly `---`. The file may open with a YAML **deck frontmatter** block, and each slide may start with its own YAML block. ```markdown --- title: Q3 Review theme: default aspectRatio: "16:9" class: font-sans --- # First slide --- layout: two-column class: bg-slate-950 id: revenue --- ## Left ::right:: ## Right ``` A `---` inside a fenced code block never splits a slide — backticks or tildes, any fence length. What *does* split a slide is a `---` you meant as a horizontal rule: use `***` or `___` for that, which markdown renders identically and the splitter ignores. A block after a separator counts as slide frontmatter only when it is at most 40 lines and **every** non-blank line is a top-level `key:` / `key: value`, an indented continuation, or a `- ` list item. Anything else — a heading, a sentence, a code fence — makes the whole block content instead. That rule is deliberately conservative: swallowing a paragraph into frontmatter would delete it from the slide with no error. **Deck keys:** `title`, `author`, `date`, `theme`, `template`, `aspectRatio`, `transition`, `class`, `keyboard`, `mouse`, `touch`, `routing`, `favicon`, `maxDuration`. **Slide keys:** `layout`, `class`, `id`, `transition`, `align`, `background`, `notes`. Any other key is forwarded to the layout component as a prop. Set `id` on slides you will link to — it is also what makes diffs stable when a deck is edited programmatically. ### The opening block is both A deck's first slide has no separator above it, so **the opening frontmatter is the deck's frontmatter and slide 1's at the same time**. Deck keys configure the deck; slide keys apply to slide 1. There is no second block to open and nothing to repeat: ```markdown --- title: Q3 Review # deck key theme: tech-dark # deck key layout: cover # slide key — slide 1 only align: center # slide key — slide 1 only notes: | # slide key — slide 1's speaker notes Thirty seconds on why we are here. --- # Q3 Review ``` `notes:` there belongs to slide 1, like every other slide key. Nothing needs a `` comment, and slide 1 does not have to store its notes differently from the rest of the deck. ### `align:` — where the content sits vertically Every layout centres its content by default, which is right for a cover and wrong for most other slides. `align:` takes `start`, `center` or `end`: ```markdown --- layout: three-column align: start --- ``` On a column layout it aligns the columns **as a group and each column individually**. That distinction is the whole point: three columns of different lengths each centring inside their own cell is what makes them stagger, so a left-to-right "step 1 → 2 → 3" slide reads as three things at three heights. `align: start` gives them a shared top edge. It is also the answer to the dead band under a `::header::`: the header sits at the top and the content floats in the middle of what is left, until you say `align: start`. Keys beginning `x-` are reserved for you: nothing reads them, and nothing warns about them, including `check --strict`. They are the place to record what a file is and how to run it, which an ordinary `.md` otherwise cannot say: ```yaml --- x-library: narro — https://getnarro.com x-run: npx @getnarro/cli dev deck.md title: Q3 Review --- ``` ## Your own CSS A `deck.css` beside the deck — or `.css` matching the deck's filename — is picked up automatically and appended to the deck's stylesheet. That is where a `@keyframes`, a `:has()` rule, or a single custom class goes. The stable hooks to target: `.rs-deck` (the deck root), `.rs-slide`, `.rs-container`, `.rs-step` (a fragment), and `.rs-layout-*` for each layout. ## Tailwind attribute syntax Attach classes, an id, or props to the preceding element with a trailing `{ … }`: ```markdown # Big title {.text-8xl .font-black .text-blue-400} A subtitle. {.text-2xl .opacity-70} ![diagram](./arch.png){.rounded-xl width=800} ``` - `{.foo}` → class - `{#foo}` → id - `{key=value}` → prop or attribute; quotes around the value are stripped - `{key}` → a boolean `true` prop, but **only** alongside at least one of the three above That last rule is what keeps the syntax compatible with MDX, where `{…}` is a JavaScript expression. A block containing at least one `.class`, `#id`, or `key=value` is treated as attributes; a block of only bare words — `{count}`, `{items.length}` — is left alone and compiled as an expression. So `{.step delay=200}` is attributes and `{delay}` on its own is not. ### Which Tailwind classes it accepts **All of them.** A class is whatever Tailwind calls it, including every part of the vocabulary that is not letters and dashes: ```markdown Arbitrary values {.text-[10px] .bg-[#0a0a0a] .grid-cols-[1fr_2fr]} Opacity modifiers {.bg-white/5 .border-amber-500/60} Fractions {.w-1/2 .basis-2/3} Variants {.hover:bg-white/10 .md:text-2xl .dark:text-white} Half-steps and overrides {.p-1.5 .!text-red-500} Arbitrary variants {.[&>*]:mt-4} ``` There is exactly one exception: a literal `{` or `}` **inside** the block, as in `{.text-[calc(1rem_+_{x})]}`. The block ends at the first `}`, so that class has to live in [`deck.css`](#your-own-css) instead. Everything else Tailwind accepts, this accepts. ### Which element it attaches to **The space decides.** Written tight against an inline element, the block is that element's; written after a space, it is the whole block's. ```markdown Only **this word**{.text-red-500} is red. The whole paragraph is red. **Not just this.** {.text-red-500} # A heading with **emphasis** {.text-7xl} ``` The third line sizes the heading, not the emphasised word — which is why the space matters. Without the rule there would be no way to write it. Because each block is resolved on its own, you can style two things differently on one line: ```markdown Compare `before`{.text-rose-400} with `after`{.text-lime-400}. ``` This works on headings, paragraphs, and the inline elements — images, links, `**strong**`, `*emphasis*`, `` `code` ``, `~~delete~~`. For a **list or a table**, put the attribute block on its own line as a separate paragraph with a blank line above it; it then attaches to the block before it. **On a list item, the block styles the `
  • `** — which is what a grid or flex row of items needs, since the class has to be on the child the parent lays out: ```markdown - Slate {.bg-sky-700 .p-6 .text-center} - Ocean {.bg-cyan-700 .p-6 .text-center} {.grid .grid-cols-2 .gap-3 .list-none .pl-0} ``` The last block is separated by a blank line and starts at column 0, so it belongs to the list rather than to the item above it. Indent it instead and it belongs to the item — to the `
  • ` when that item holds a single block, and to the block it follows when the item holds several. Tight and loose lists behave the same way; the blank lines between items make no difference. Run `check --render` if you are unsure which one you got. It prints the element each class actually landed on, and a class that landed on nothing at all is an error rather than something you find on the projector. **A size on a container reaches the text inside it.** These are the same: ```markdown :::{.text-[8rem]} A big line. ::: A big line. {.text-[8rem]} ``` Worth stating because it is not free, and because for one release it was not true. The deck's base stylesheet sets `.rs-deck p { font-size: 1.5rem }`, which styles the paragraph *directly* — and a value on an ancestor can only arrive by inheritance, which a direct rule always beats. So the compiler marks a container that sets `font-size`, `line-height`, `font-family` or `letter-spacing`, and the base rules stand down for the elements inside it. It marks the classes that set those properties and nothing else, because `text-` is three different things. `text-4xl` and `text-[8rem]` are sizes; `text-center` and `text-cyan-300` are not, and neither changes the size of anything below them. `font-mono` is a family; `font-bold` is not. One case is deliberately left out: `text-[var(--x)]`, where narro cannot tell a size from a colour. Write `text-[length:var(--x)]` and it will. Code fences are exempt. A `pre` keeps its own size inside a sized card, because a fence that inherited a display size from the card around it would be unreadable. **In a table, it styles the cell** — `| Total {.text-right .font-black} |` puts the classes on that `` or ``. Repeated blocks on one element merge: classes concatenate, and a later `#id` or `key=value` wins. One thing the syntax cannot do: style an *empty* element. `** **{.w-8}` does not parse, because emphasis needs non-space content. For a coloured rectangle, a bar, or a spacer, use an **empty container** — it takes attributes like any other and needs nothing but markdown: ```markdown :::{.h-2 .w-48 .rounded-full .bg-cyan-400} ::: ``` An inline `` works too and is the shorter answer inside a sentence — see [MDX](#mdx) below. ### On a fenced code block An attribute block in the info string styles the `
    `; the `language-*` class
    on the inner `` is left alone.
    
    **A fence with a language is syntax highlighted**, tokenised when the deck
    compiles. Nothing ships to the browser to do it — what the bundle carries is
    coloured markup — so highlighting costs the deck a couple of kilobytes and no
    runtime at all. A fence with no language, or one naming a language with no
    grammar, renders as plain text; neither is an error.
    
    ````markdown
    ```bash {.rounded-xl .bg-black .p-8}
    npx @getnarro/cli dev deck.md
    ```
    ````
    
    The separate-paragraph form works too, and is the one to use when the fence
    already carries other metadata.
    
    ## Grouping blocks
    
    `:::` opens a container and a bare `:::` closes it. It takes the same attribute
    syntax as any element, and containers nest:
    
    ```markdown
    :::{.flex .items-center .gap-8}
    ### 72 KB {.text-6xl .font-black}
    
    Gzipped, including the runtime.
    :::
    ```
    
    This is how you get a row, a grid, or a bordered box without leaving markdown.
    Before it existed, every horizontal arrangement had to be a `
      ` carrying `{.flex .list-none .pl-0}` — two utilities of pure boilerplate to undo list styling — and anything a list could not express was out of reach. Every container carries `rs-container`, so a stylesheet can target them. **A slide is MDX, so `` in prose is a component reference.** Writing a component tree as `` / `` is a build error naming a line, for text that is obviously prose — put it in `` `inline code` `` (which also looks right) or escape the `<`. Ordinary prose is safe: `<50 KB` and `press <- to go back` both compile, and an autolink like `` still works. See [MDX](#mdx). Four things worth knowing before you build a grid out of these: - **`:::` must start at column 0.** An indented `:::` is not a container marker; it stays on the slide as literal text (and `check` reports it). - **They nest by order, not by indentation.** A bare `:::` closes the innermost container still open, and since none of them can be indented, a four-deep tree is four opening lines and four identical closers. If you write one closer too few, Narro closes the container at the end of the slide rather than swallowing everything after it — and `check` warns you it had to, naming the line the container opened on. Under `--strict` that warning fails the build. - **Blank lines inside are optional.** The compact form above and the spaced form (a blank line after the opening fence and before the closing one) compile identically, at any nesting depth. Use whichever reads better. - **There is no iteration.** Twelve cards is twelve containers; a deck cannot loop, reference-and-repeat, or define a fragment to reuse. A twelve-item grid is around 120 lines of markdown, and that is the point at which [MDX](#mdx) and a `.map()` — 15 lines — is the better tool. This is the sharpest edge of the mode; see [Limitations](/docs/limitations.md). ## Diagrams A ```` ```diagram ```` fence draws boxes joined by arrows. It is a fence rather than a component so that it works everywhere markdown does, including [single-file mode](#single-file-mode): no import, no props, no companion file. ````markdown ```diagram Sources -> *Agent*: fetch Docs -> *Agent* Schema -> *Agent* *Agent* -> Deck *Agent* -> Speaker notes ``` ```` Every line is one of five things: | Line | Means | | --- | --- | | `A -> B` | An arrow from `A` to `B`. Both boxes are created by being named | | `A -> B: label` | The same, with `label` on the arrow. The label belongs to the arrow *into* `B` | | `A -> B -> C` | A chain; the same as writing two lines | | `A` | A box on its own, with no arrows — a legend, or a node you will point at from prose | | `direction: down` | Lay the graph out top-to-bottom instead of left-to-right | | `scale: 0.6` | Draw the whole diagram at 60%, layout included | Plus blank lines and `#` comments, which are ignored. - **A name with spaces goes in quotes:** `"Design tokens" -> Deck`. - **`*Name*` emphasises a box**, drawing it in the theme's accent. Marking it once is enough, wherever the name appears. - **A name is an identity.** Writing `Agent` on four lines makes one box with four arrows, which is how a fan-in is written. The layout comes from the arrows: nodes with nothing pointing at them start the first layer, and everything else sits one layer past whatever points at it. So chains, fan-in, fan-out, trees and two-column mappings are all the same thing to it, and none of them needs anything said about position. Nothing ships to the browser. The graph is laid out and drawn when the deck compiles, and what the bundle carries is an `` for the boxes and arrows with ordinary HTML for the labels — so the text inherits the deck's font and is selectable, and there is no layout engine to load. Colours come from `currentColor` and the theme's accent, so a diagram follows `theme:` rather than pinning it. ### When a diagram does not fit A diagram is sized by its content — that is what lets it be drawn without a browser — so a deep `direction: down` graph can come out taller than the canvas. `scale:` is the lever, and it shrinks the box as well as the drawing: ````markdown ```diagram direction: down scale: 0.55 App -> Header -> Nav -> Menu -> Item ``` ```` Anything from `0.3` to `2`. A Tailwind size utility on the fence will not do it (the measured size is an inline style and wins), and `transform: scale()` in a stylesheet shrinks the picture while the element keeps its original footprint — so the slide still overflows. `scale:` does both, which is why it is a directive rather than something to reach for CSS for. Below about `0.4` a label stops being readable from the back of a room; at that point the diagram wants fewer nodes rather than a smaller scale. ## Charts A ```` ```chart ```` fence draws a horizontal bar chart, for the same reason `diagram` is a fence: it works at the floor, with no import and no companion file. ````markdown ```chart unit: " KB" React 19: 96.5 Vue 3: 78 Ours: 72 * ``` ```` | Line | Means | | --- | --- | | `Label: 12.5` | One bar | | `Label: 12.5 *` | The same, drawn in the theme's accent — the bar the slide is about | | `unit: " KB"` | Appended to every printed value. Quote it to keep a leading space | | `max: 100` | The value a full-length bar represents. Without it each chart is drawn against its own largest bar, so two charts on two slides are not comparable | Plus blank lines and `#` comments. **The proportions are computed from the numbers you wrote**, which is the whole point: the alternative is a row of `:::` containers holding `w-[38%]`, `w-[51%]` and `w-full`, worked out by hand and silently wrong the next time a number changes. Bars are proportional to each other and to nothing else — there is no axis and no scale reference, the same limit `` has. A bar chart here is for the comparison, not the measurement; see [Limitations](/docs/limitations.md#chart-is-a-basic-implementation). For a pie, a line, or a chart with a legend, import `` from `@getnarro/shared-ui` — which needs [MDX](#mdx) and therefore a project. **What it will not draw** — a cycle, a hand-placed box, an edge routed around something — is in [Limitations](/docs/limitations.md), along with what to use instead. A line it cannot read is an error from `check`, never a box quietly left out. The one thing to know about the standalone-box form is that it makes a stray line of prose inside the fence into a box rather than into an error — a sentence with no `->` is a legal node name. If you want a box only where you drew an arrow, keep the fence to arrows. ## Layout slots `::name::` on its own line starts a named slot. Content before the first marker is the `default` slot. Slots arrive at the layout component as props. ```markdown --- layout: two-column --- Goes to `children`. ::right:: Goes to the `right` prop. ``` **Every built-in layout takes `::header::` and `::footer::`** — full-width rows above and below the content, whatever the layout puts in between: ```markdown --- layout: two-column --- ::header:: ## Before and after {.text-5xl} ::default:: The old way. ::right:: The new way. ``` Both rows render only when filled, so a deck that never uses them is unchanged. Title-above-two-panels is the most common shape a deck asks for, and they were column-layout-only for long enough that decks on `default` and `cover` hand-stacked paragraphs to fake a header. An eyebrow line above a cover title, a source note under a chart, and a page footer are all this. `::default::` reopens the default slot, which is how you write a header first and then the main content. ## Transitions `transition:` takes one of `none`, `fade`, `slide`, `zoom` — on a slide, or on the deck to change the default for every slide. `fade` is the default. ```markdown --- title: My Deck transition: zoom --- ``` `narro check` rejects any other name. `TransitionType` in `@getnarro/shared-ui` is a larger, *different* set belonging to the standalone `SlideTransition` component; frontmatter does not accept it. ## Speaker notes ```markdown ``` The comment can sit anywhere in the slide, and a slide may carry more than one — they are concatenated in source order, separated by a blank line. The comments are removed before compilation, so nothing reaches the slide. The `notes:` frontmatter key is the alternative, and is the better one when the text is long enough to want YAML's block scalar: ```markdown --- notes: | Open with the customer story. Then the number. --- ``` Either way the text shows up in the presenter view and nowhere else. The two are not interchangeable: the comment form is a single line, and the frontmatter form is the one that takes YAML's block scalar. Reach for `notes:` as soon as the text runs past a sentence. ## Fragments Reveal content step by step with `.step`: ```markdown - appears first {.step} - appears second {.step delay=200} ``` `delay` is milliseconds, and it is **consumed** by the step wrapper rather than forwarded — it will not appear as an attribute on the element. Each marked node is wrapped at compile time, and reaches the DOM as: ```html
      ``` Those hooks are the supported way to target fragments from a theme or a custom stylesheet. The wrapper is `display: contents`, so it takes no part in layout — a revealed row of cards is `{.grid .grid-cols-3}` on the list and nothing else, and each `
    • ` is still the grid's own child. `.step` works on a list item, a paragraph, a fenced block and a `:::` container: ```markdown :::{.step .rounded-xl .border .p-8} ### A card that reveals With a heading and a paragraph inside it. ::: ``` It can also go on its own line below the block it should reveal, which is the form to use when the block already carries a long attribute list. `.step` is the only reserved class name in the attribute syntax: every other `{.foo}` is passed through as a literal class. A `.step` that reaches no element is an error rather than a reveal that silently is not there. ## Layouts Reference a layout by name in slide frontmatter. **Slide 1's frontmatter is the deck's frontmatter** — they are the same YAML block — so the title slide names its layout there, alongside `title` and `theme`: ```markdown --- title: My Deck theme: tech-dark layout: cover --- # My Deck ``` Keys that are legal on a deck *and* on a slide — `class`, `transition` — still mean the deck when written there. To set one on slide 1 only, open a second `---` block immediately after the frontmatter; it becomes slide 1's own and creates no empty slide. A name resolves in order, first match winning: 1. `/layouts/.{tsx,jsx,mdx,ts,js}` — yours, shadows everything 2. The [deck template](/docs/deck-templates)'s layouts, if the deck has one 3. The active theme's layouts 4. The built-ins: | Layout | Slots | | --- | --- | | `default` | `header`, `footer` | | `cover` | `header`, `footer` | | `section` | `header`, `footer` | | `quote` | `header`, `footer` | | `end` | `header`, `footer` | | `two-column` | `left`, `right`, `header`, `footer` | | `three-column` | `left`, `two`, `three`, `middle`, `right`, `header`, `footer` | | `image-right` | `header`, `footer` | | `image-full` | `header`, `footer` | That table is generated from `builtinLayoutSlots`, which is also what the validator checks against — so a slot name it does not list is one `narro check` will reject, and content after it would not have rendered. Two things the table does not show: **Every column answers to two names, and both column layouts name them the same way.** On `three-column` the middle is `::middle::` or `::two::` and the right is `::right::` or `::three::`; on both layouts the left column is the default slot or `::left::`. `::left::` renders *after* the default slot rather than replacing it, so a slide can name all of its columns explicitly or lean on the default for the first one. The positional names came first, and they were the trap: `two-column` has a slot called `right`, so `::right::` on a three-column slide was the guess everyone made once and it reached no column. Both guesses are correct now. **The image layouts take their image from frontmatter, not a slot.** `image-right` and `image-full` read the slide's `image:` key, falling back to `background:` — which is why neither has a named slot: ```markdown --- layout: image-right image: ./architecture.png --- ## What changed The old pipeline is on the right. ``` A layout is a plain React component: ```tsx import type { SlideLayoutProps } from "@getnarro/markdown/runtime"; export default function TwoColumn({ children, right }: SlideLayoutProps) { return (
      {children}
      {right}
      ); } ``` ### Naming your own layouts without writing components A [deck template](/docs/deck-templates) is one file beside the deck that defines layouts as data — a built-in plus classes, defaults, and the chrome every slide carries — and gives each one an id the markdown references: ```ts // deck.template.ts export default { id: "acme", master: { footer: { text: "{title} · confidential" } }, layouts: { "metric-split": { base: "two-column", regions: { right: "text-right text-accent" } }, }, }; ``` ```markdown --- layout: metric-split --- ``` It is the slide master: `narro check` knows the ids, the deck stops naming colours, and rebranding is one file. ## MDX A slide is MDX, whatever the file is called. Components, JSX and `{expression}` work in a `.md` deck exactly as they do in a `.mdx` one. Put `import` and `export` statements in the **preamble**: the statements at the top of the file, after the deck frontmatter and before the first slide. They are shared by every slide, and the preamble ends where the statements do — a `---` after them is allowed but not required. Anything else up there is an error rather than a slide that quietly disappears. An import inside a slide body is also an error; move it to the preamble. Two consequences of slides being MDX, both of which produce a build error naming the line rather than a surprise on the projector: - **HTML is JSX.** Every tag closes (`
      `, not `
      `) and attributes are camelCased (`className`, not `class`). - **`{` opens an expression** unless the block is attribute syntax. Write `\{` for a literal brace, or put the text in `` `inline code` ``. ```markdown import { LiveChart } from './components/chart' export const Stat = ({ n, label }) => (
      {n}{label}
      ) # Revenue ``` ## A stylesheet, without leaving the mode A `deck.css` beside the deck — or `.css`, matching its name — is picked up automatically. No import, no config, no build step. It is where a `@keyframes`, a `:has()` rule, or a connector pseudo-element goes, and it is the **first thing to reach for before writing a component**: one extra file buys back most of what plain markdown cannot say, and the deck stays a markdown deck. Three stable hooks the compiler guarantees: `.rs-slide` for a slide, `.rs-container` for a `:::` container, `.rs-step` for a fragment. ```css /* deck.css */ @keyframes cursor { 50% { opacity: 0; } } .cursor::after { content: "█"; animation: cursor 1s step-end infinite; } ``` ## Formatting a deck Run `narro fmt deck.md`. **Do not point a general markdown formatter at a deck.** The reason is [the space rule](#which-element-it-attaches-to): `**word**{.red}` styles the word and `**word** {.red}` styles the block, so any formatter that reflows a paragraph can move an attribute block onto its own line and silently change what it applies to. Prettier does exactly that. Nothing errors, `check` sees a legal deck, and the design is different. `narro fmt` does only what cannot change rendering: runs of blank lines, `---` separators, YAML colon spacing, trailing whitespace, and collapsing multiple spaces before an attribute block — preserving tightness, which is the one thing that carries meaning. `narro fmt --check` is the CI form. One piece of trailing whitespace is *not* invisible: two spaces at the end of a line is CommonMark's hard line break. `fmt` rewrites those as a trailing `\`, which renders identically, survives the next `fmt`, and is visible in a diff. Write `\` yourself and it is left alone. If your repo runs Prettier over everything, add the deck to `.prettierignore`. The scaffolded markdown templates already do. ## In a scaffolded project `markdown-minimal` is the deck and nothing else — `deck.md`, and a `package.json` that pins the CLI. The `markdown-app` and `markdown-docs` templates wire the same plugin into a normal Vite project, which is what you want when the deck needs its own dependencies or pinned versions: ```ts // vite.config.ts import path from "node:path"; import { narroMarkdown } from "@getnarro/markdown/vite"; import tailwindcss from "@tailwindcss/vite"; import react from "@vitejs/plugin-react"; import { defineConfig } from "vite"; export default defineConfig({ plugins: [ narroMarkdown({ deckPath: path.resolve(import.meta.dirname, "deck.md") }), react(), tailwindcss(), ], resolve: { dedupe: ["react", "react-dom", "@getnarro/core"] }, }); ``` The plugin exposes the compiled deck as `virtual:narro/deck` and its stylesheet as `virtual:narro/deck.css`. Its full option list — including `strict`, which promotes validation warnings to errors and belongs in CI — is on the [markdown API](/docs/markdown-api#the-vite-plugin) page. ### What `lint` and `typecheck` mean for a deck Every template exposes the same script names, because a monorepo's pipeline calls them by name and a project that answers one of them with nothing is a hole in the pipeline. For a markdown deck the two that need translating are: | Script | For a deck with components (`markdown-app`, `markdown-docs`) | For a deck that is one `.md` (`markdown-minimal`) | | --- | --- | --- | | `lint` | `narro check deck.md --strict` plus Prettier over `components/` | `narro check deck.md --strict` | | `typecheck` | `tsc --noEmit` | `narro check deck.md --strict` — there is no TypeScript, and the dialect check is the equivalent gate | `build` in every template ends in `narro check dist --fit-if-available`, so a machine with a browser measures the built deck on every build and one without says so instead of failing. **A deck from `narro new` has no scripts at all**, so `narro build deck.md` does the measuring itself — same behaviour, no `package.json` required. Neither `lint` nor `typecheck` can see a clipped slide: they both map to `--strict`, and `--strict` is about names. If you wire your own pipeline, the fit check is the one that has to be in it, because it is the only one that opens a browser. ## Driving the format from code Everything on this page is also reachable as a library. `@getnarro/markdown` parses, edits, validates, and compiles decks, and it is what the CLI and the MCP server are built on — a deck can be read and rewritten without a regex. The one distinction worth knowing before you start: `splitDeck` parses for *rendering* and loses the original YAML text, while `parseDeckDocument` parses for *editing* and round-trips exactly, so a tool can change one slide without reformatting the other forty. See [markdown API](/docs/markdown-api). ## One dialect Narro has exactly one markdown dialect: this one. Older material described a directive syntax (`::text[…]{size="lg"}`, `:::three-columns`) that was never part of the shipping pipeline — that implementation has been removed. If you find those directives in a deck, they will render as literal text. --- # Writing AI Prompts > How to brief an AI assistant so the deck it produces actually builds > > Read this when: What do I tell an AI so the deck it writes actually builds? Narro is text, so an AI can write a deck directly. What separates a usable result from a broken one is mostly the setup, not the prose of the prompt. ## Give the model the dialect first The single biggest failure mode is a model writing markdown in a syntax Narro does not speak — usually one it half-remembers from Marp or reveal.js. Point it at the reference before asking for slides: > Read , then write a deck about … Better still, paste the dialect contract from [Rules for AI agents](/docs/agent-rules) into your project's `AGENTS.md` — a link is read once, a contract in the repository is read every session. For offline or sandboxed agents, install the docs instead: ```bash npm install --save-dev @getnarro/docs ``` `node_modules/@getnarro/docs/generated/llms-full.txt` is every page in one file, `catalog.json` is every name a deck may use, and `components.json` is the component API generated from the TypeScript source — none of the three can be out of date with the version installed. ## Prefer markdown mode Ask for a `.md` deck unless you specifically need React. It is one file, the failure modes are visible, and `npx @getnarro/cli build deck.md` gives a pass/fail signal the model can act on. ## Make the model verify A deck that "looks right" can still be broken, so close the loop: > Write the deck to `deck.md`, run `npx @getnarro/cli check deck.md` after each > edit, and `npx @getnarro/cli build deck.md` at the end. Fix anything either > reports. `check` validates names — layouts, slots, themes — in about a second, and names the valid options when one is wrong. `build` is the slower, complete answer. In React mode the equivalent is `npm run build`. Without these nothing catches a mistyped prop, a layout that does not exist, or a component imported from the wrong package. ## Be specific about the deck, not the styling Narro's themes handle styling. What a model cannot guess: - **Audience and level** — "staff engineers who have not used Kafka" - **Length** — "8 slides" - **Arc** — "problem, why the obvious fix fails, our approach, results, ask" - **Content it cannot invent** — real numbers, names, the actual results ### A prompt that works > Read . Write an 8-slide deck at > `deck.md` introducing our migration from REST to gRPC, for backend engineers > who have not used gRPC. Arc: what hurt about REST at our scale, why we did not > just add caching, what gRPC changed, the migration path, the p99 numbers > (340ms → 95ms), what we would do differently. Confident but not salesy. Use > `layout: section` between chapters. Then run > `npx @getnarro/cli build deck.md` and fix any errors. ### A prompt that does not > Make a presentation about gRPC. You get ten slides of generic bullets, in whatever dialect the model guessed. ## Two things models get wrong **Which package a component comes from.** `@getnarro/core` exports the presentation runtime and the motion and canvas primitives — exactly `AnimatePresence`, `Canvas`, `CanvasElement`, `CanvasShape`, `ErrorBoundary`, `ErrorOverlay`, `MarqueeRow`, `MasonryBackground`, `Motion`, `MotionContainer`, `MotionList`, `MotionNumber`, `MotionPresence`, `MotionSpotlight`, `MotionStep`, `MotionSteps`, `MotionText`, `MotionTransform`, `NebulaBackground`, `Notes`, `OverviewGrid`, `Presentation`, `PresenterNotes`, `Slide`, `SlideContent`, `TransformSlide`, `TrustedByMarquee`. Everything else, `Heading` and `Text` and `List` and the rest, is in `@getnarro/shared-ui`. Importing `Heading` from core is the most common broken deck. **Prop values that sound plausible.** `SlideContent`'s `layout` is "default" | "centered" | "top" | "bottom" | "fill" | "between". `Slide`'s `layout` is a different set. `List`'s `variant` is "disc" | "decimal" | "check" | "arrow" | "none" — not `"bullet"` or `"number"`. The [component reference](/docs/components) is generated from the types, so it is the authority. ## The MCP server [`@getnarro/mcp-server`](/docs/ai-integration) gives an assistant Narro-specific tools — scaffolding, listing and editing slides, exporting — and serves this documentation as MCP resources. Useful for assistants without their own shell access; a coding agent that already has a terminal can just use the CLI. --- # Rules for AI Agents > Copy-paste contracts for your AGENTS.md, CLAUDE.md, or .cursor/rules > > Read this when: What do I paste into my repo so an agent stops writing decks that build wrong? This page is a set of blocks meant to be **pasted into your own project's `AGENTS.md`, `CLAUDE.md`, or `.cursor/rules`** — not a description of how Narro works. A project scaffolded with `npm create narro@latest` already has all of this in its `AGENTS.md`; this page exists for the case where you added Narro to a repository that already existed, so nothing wrote that file for you. Paste the two contracts below and the verification loop. Each one prevents a specific failure that a model, left to its own defaults, produces reliably. ## Choose the entry point before you scaffold The first decision, and the one most often made wrong — a deck ends up inside a Vite application it never needed, and every later question is about the project rather than the deck. | The deck is | Use | You get | | --- | --- | --- | | Prose, lists, code, tables — one file (**single-file mode**) | `npx @getnarro/cli new deck.md` | `deck.md`, nothing else | | The same, but version-pinned in a repo | `npm create narro@latest x -- --template markdown-minimal` | `deck.md` + `package.json` | | Needs its own dependencies or React layouts | `npm create narro@latest x -- --template markdown-app` | the deck inside a Vite project | | Live components, data, interaction | `npm create narro@latest x` | a React deck | A single markdown file in an empty directory is a complete deck: the CLI brings its own runtime, so `npx @getnarro/cli dev deck.md` needs no `package.json`, no `node_modules`, and no config. Custom layouts (`layouts/.tsx`) and custom CSS (`deck.css`) both work beside a bare `deck.md` — reaching for a project is about dependencies, not capability. If the deck must stay **one file** — pasted into an issue, committed beside the code it documents, handed to someone who will not run an install — that is a constraint worth stating up front, because it rules out iteration, per-slide state, custom CSS and anything needing a build step. [Single-file mode](/docs/markdown-mode#single-file-mode) says what is available and what to write instead. ## Why a contract rather than a link to the docs A model that has not read Narro's documentation falls back on the presentation tooling it has seen most: Marp, reveal.js, Slidev. Those have a different markdown dialect and a different component model, and the resulting deck **builds and renders wrong rather than failing** — directives appear as literal text on the slide, a heading is imported from the wrong package, a layout name that never existed silently falls back to the default. Nothing throws, so nothing tells the model to try again. The contracts below are written to be retrieved by the next agent that is about to make one of those mistakes. ## Contract 1 — the markdown dialect ````markdown ## Narro markdown rules Narro is not Marp, reveal.js, or Slidev. Their syntax does not work here and fails silently — a directive Narro does not recognise renders as literal text on the slide. 1. Slides are separated by a line of exactly `---`. A `---` inside a fenced code block does not split a slide. 2. The file opens with YAML deck frontmatter; each slide may carry its own frontmatter block immediately after its separator. 3. Attach styling with `{.class}` after an element — `# Revenue {.text-7xl}`. These are Tailwind classes. There is no `` and no `