Component reference

What props does this component take, and which package do I import it from?

83 min readView as markdown46 chunks · 25.5k tokens in docspack

85 components ship across 5 packages. Props below are read out of the type definitions, so they match the installed version exactly.

@getnarro/atlas/react

2 components, imported from @getnarro/atlas/react.

<Atlas>

import { Atlas } from "@getnarro/atlas/react";

A zooming canvas.

Behaviour. Fills its container and clips to it, so it wants a slide of its own. Its children are placed in world units on a plane the camera moves over rather than laid out by the slide — AtlasNode is what places them — and a node the camera cannot see, or that its level-of-detail band excludes, is unmounted rather than merely hidden.

<Atlas path={{ waypoints: [
  { id: "whole", at: { cx: 0, cy: 0, w: 4000 } },
  { id: "ingest", at: "#ingest" },
] }} nodes={nodes}>
  <div data-atlas-id="ingest">…</div>
</Atlas>
PropTypeRequiredDescription
pathAtlasPathyesThe camera path.
activeboolean—Only listen while this is true — a deck passes its slide’s isActive.
childrenReactNode—What to render on the plane. Node ids come from data-atlas-id.
classNamestring—
initialWaypointstring | number—Which waypoint to start at.
keyboardboolean—Take the arrow keys. Uses the same contract as Slide’s sub-slides: capture the key, and swallow it only while there is another waypoint to move to, so the deck advances normally at either end. Without that, the deck and the canvas both act on one press.
nodesreadonly PlacedNode[]—Nodes on the default plane. Ignored when provider is given.
onArrive((id: string, index: number) => void)—Called on arrival at each waypoint.
providerSceneProvider—The scene. Defaults to a plane built from nodes.
styleCSSProperties—
viewportViewport—The coordinate grid the world is measured in. This is the world, not the surface it is painted on: it fixes what at: 0, 0, 4000 means, and it is what a reference resolves against. The actual pixel size of the canvas is measured, so a deck rendered at any size frames the same content.

<AtlasNode>

import { AtlasNode } from "@getnarro/atlas/react";

A node on the plane. contain: layout paint is not decoration: it stops a node’s own layout from escaping into the camera root, which is what keeps a frame at one style recalculation and zero layout however many nodes are mounted.

Behaviour. Positions itself absolutely at x/y in world units, so it only works inside an Atlas and ignores whatever layout surrounds it. It renders nothing at all while the camera cannot see it, or while its lod band excludes the current framing.

PropTypeRequiredDescription
heightnumberyes
idstringyesMust match a PlacedNode.id, or the node is never culled.
widthnumberyes
xnumberyesWhere it sits, in world units.
ynumberyes
childrenReactNode—
classNamestring—
styleCSSProperties—

@getnarro/atlas/video

One component, imported from @getnarro/atlas/video.

<AtlasScene>

import { AtlasScene } from "@getnarro/atlas/video";

A zooming canvas as a video scene. tsx <AtlasScene path={path} nodes={nodes}> <AtlasNode id="ingest" x={0} y={0} width={400} height={300}>…</AtlasNode> </AtlasScene>

Behaviour. Fills its container absolutely and owns the whole frame, so it cannot share a scene with ordinary content. Its children are positioned in world units on a plane the camera moves over — AtlasNode is what places them — and anything outside the frame is unmounted rather than merely hidden.

PropTypeRequiredDescription
pathAtlasPathyes
childrenReactNode—
classNamestring—
holdnumber—A rest at every waypoint that does not ask for one, in seconds. A video with no holds is a camera that never stops, which is unwatchable. The deck host has no equivalent because a presenter supplies the pauses.
nodesreadonly PlacedNode[]—
providerSceneProvider—
styleMotionStyle—
viewportViewport—The world grid. Defaults to the composition’s own pixel size.

@getnarro/core

27 components, imported from @getnarro/core.

<AnimatePresence>

import { AnimatePresence } from "@getnarro/core";

AnimatePresence enables the animation of components that have been removed from the tree. When adding/removing more than a single child, every child must be given a unique key prop. Any motion components that have an exit property defined will animate out when removed from the tree. jsx import { motion, AnimatePresence } from 'framer-motion' export const Items = ({ items }) => ( <AnimatePresence> {items.map(item => ( <motion.div key={item.id} initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }} /> ))} </AnimatePresence> ) You can sequence exit animations throughout a tree using variants. If a child contains multiple motion components with exit props, it will only unmount the child once all motion components have finished animating out. Likewise, any components using usePresence all need to call safeToRemove.

<Canvas>

import { Canvas } from "@getnarro/core";

A container for absolutely positioned elements

Behaviour. A positioning context: children are placed absolutely against it, so it only works with CanvasElement/CanvasShape inside. Reach for it when no layout component expresses the arrangement.

<Canvas>
  <CanvasElement x={10} y={20} width={200} height={100}>
    <div>Positioned content</div>
  </CanvasElement>
  <CanvasShape shape="circle" x={50} y={50} size={100} color="#3b82f6" />
</Canvas>
PropTypeRequiredDescription
childrenReactNodeyesChildren elements to position
backgroundstring—Background color
classNamestring—Additional CSS classes
heightstring—Height of the canvas (default: 100%)
widthstring—Width of the canvas (default: 100%)

<CanvasElement>

import { CanvasElement } from "@getnarro/core";

A positioned element within a Canvas

Behaviour. Absolutely positioned against the nearest Canvas. x/y are the top-left corner as a percentage or a length, not a centre point, and width/height default to the content’s own size.

<CanvasElement x={10} y={20} width={200} height={100}>
  <div>Positioned at 10%, 20%</div>
</CanvasElement>
// Using pixels:
<CanvasElement x="100px" y="50px" width="200px" height="100px">
  <div>Positioned at 100px, 50px</div>
</CanvasElement>
PropTypeRequiredDescription
childrenReactNodeyesChildren content
xPositionValueyesX position (% or px)
yPositionValueyesY position (% or px)
classNamestring—Additional CSS classes
heightPositionValue—Height (% or px, optional)
opacitynumber—Opacity (0-1)
rotatenumber—Rotation in degrees
widthPositionValue—Width (% or px, optional)
zIndexnumber—Z-index for layering

<CanvasShape>

import { CanvasShape } from "@getnarro/core";

A pre-built shape positioned on the Canvas

Behaviour. Absolutely positioned against the nearest Canvas, and always square — size sets both axes. rotate turns the SVG in place and does not change the box it occupies.

<Canvas>
  <CanvasShape shape="circle" x={50} y={50} size={100} color="#3b82f6" />
  <CanvasShape shape="star" x={200} y={100} size={80} color="#fbbf24" />
  <CanvasShape shape="arrow" x={300} y={50} size={120} color="#10b981" rotate={45} />
</Canvas>
PropTypeRequiredDescription
shapeShapeTypeyesShape type
sizenumberyesSize of the shape
xPositionValueyesX position (% or px)
yPositionValueyesY position (% or px)
classNamestring—Additional CSS classes
colorstring—Fill color
opacitynumber—Opacity (0-1)
rotatenumber—Rotation in degrees
strokestring—Stroke color
strokeWidthnumber—Stroke width
zIndexnumber—Z-index for layering

<ErrorBoundary>

import { ErrorBoundary } from "@getnarro/core";

Error Boundary component that catches errors and displays the overlay

<ErrorBoundary onError={(error) => console.error(error)}>
  <Presentation>
    <Slide>...</Slide>
  </Presentation>
</ErrorBoundary>
PropTypeRequiredDescription
childrenReactNodeyesChildren to render
fallbackReactNode | ((error: ErrorDetails, reset: () => void) => ReactNode)—Fallback component to render on error (optional, defaults to ErrorOverlay)
onError((error: Error, errorInfo: ErrorInfo) => void)—Callback when an error occurs
showOverlayboolean—Enable development overlay (default: true in dev, false in prod)

<ErrorOverlay>

import { ErrorOverlay } from "@getnarro/core";

Error overlay component for development

<ErrorOverlay
  error={{
    message: "Something went wrong",
    stack: "Error: Something went wrong\n  at Component ..."
  }}
  onDismiss={() => setError(null)}
/>
PropTypeRequiredDescription
errorErrorDetailsyesError details to display
onDismiss(() => void)—Callback to dismiss/reset the error
showDismissboolean—Show dismiss button (default: true)

<MarqueeRow>

import { MarqueeRow } from "@getnarro/core";

One scrolling row of a marquee. Usually you want TrustedByMarquee, which arranges several of these.

Behaviour. One scrolling row of a TrustedByMarquee, full width and sized by its content.

PropTypeRequiredDescription
itemsMarqueeItem[]yesItems to display in the marquee
classNamestring—Additional CSS class for the container
direction"left" | "right"—Scroll direction
gapnumber—Gap between items in pixels (default: 16)
isActiveboolean—Whether the animation is active
itemMinWidthnumber—Item minimum width in pixels (default: 180)
renderItem((item: MarqueeItem, index: number) => ReactNode)—Custom item render function
speednumber—Scroll speed (pixels per frame, default: 0.5)

<MasonryBackground>

import { MasonryBackground } from "@getnarro/core";

A full-bleed masonry grid of images that crossfades between them. Pauses while its slide is off screen, so an idle deck does no work.

Behaviour. Fills its positioning parent behind the content, in columns sized by the count you pass.

<MasonryBackground
  images={["/a.jpg", "/b.jpg", "/c.jpg"]}
  gridColumns={12}
  gridRows={6}
  swapMinIntervalMs={3000}
  swapMaxIntervalMs={6000}
/>
PropTypeRequiredDescription
imagesstring[]yesArray of image URLs to display and cycle through
backgroundColorstring—Background color (default: “black”)
classNamestring—Additional CSS class for the container
gridColumnsnumber—Number of grid columns (default: 12)
gridRowsnumber—Number of grid rows (default: 6)
imageBorderColorstring—Border color for images (default: “rgba(255, 255, 255, 0.6)“)
imageBorderWidthstring—Border width for images (default: “1px”)
imageGapnumber—Gap between images in pixels (default: 6)
initialFillTimeMsnumber—Time in ms to fill the page with initial images (default: 800)
isActiveboolean—Whether the animation is active
swapMaxIntervalMsnumber—Maximum time in ms between image swaps after initial fill (default: 2000)
swapMinIntervalMsnumber—Minimum time in ms between image swaps after initial fill (default: 1000)
tileLayoutTileLayout[]—Grid tile layout configuration
transitionDurationMsnumber—Duration of crossfade transition in ms (default: 800)

<Motion>

import { Motion } from "@getnarro/core";

A Framer Motion animation wrapper Provides smooth, physics-based animations with 10+ built-in effects.

<Motion effect="slideUp" duration={0.5}>
  <h1>Animated Title</h1>
</Motion>
PropTypeRequiredDescription
childrenReactNodeyesChildren to animate
classNamestring—Additional class names
delaynumber—Animation delay in seconds
durationnumber—Animation duration in seconds
easingMotionEasing—Easing function
effectMotionEffect—Animation effect to apply
inViewboolean—Whether to trigger animation only when in view
isActiveboolean—Whether animation has played (for controlled animations)
onAnimationComplete(() => void)—Callback when animation completes
respectReducedMotionboolean—Enable reduced motion fallback
styleCSSProperties—Additional styles

<MotionContainer>

import { MotionContainer } from "@getnarro/core";

Container for staggered child animations

Behaviour. Measures its content and animates its own height between states, so it must not be given a fixed height by a parent — the animation is that height changing.

<MotionContainer staggerDelay={0.1}>
  <Motion effect="slideUp">Item 1</Motion>
  <Motion effect="slideUp">Item 2</Motion>
  <Motion effect="slideUp">Item 3</Motion>
</MotionContainer>
PropTypeRequiredDescription
childrenReactNodeyesChildren to animate with stagger
classNamestring—Additional class names
isActiveboolean—Whether children should animate
staggerDelaynumber—Stagger delay between children in seconds
styleCSSProperties—Additional styles

<MotionList>

import { MotionList } from "@getnarro/core";

MotionList - Staggered list animation

PropTypeRequiredDescription
itemsReactNode[]yesList items
classNamestring—Additional class names for container
effectMotionEffect—Animation effect for each item
isActiveboolean—Whether animations are active
itemClassNamestring—Additional class names for items
listStyle"none" | "disc" | "decimal" | "check"—List style type
staggerDelaynumber—Stagger delay between items in seconds

<MotionNumber>

import { MotionNumber } from "@getnarro/core";

Animated number counter

Behaviour. Animates to its target and reserves no space for the widest value it will pass through, so a number that grows in digit count shifts the layout around it.

<MotionNumber value={1000000} prefix="$" separator duration={2} />
PropTypeRequiredDescription
valuenumberyesTarget value
classNamestring—Additional class names
decimalsnumber—Number of decimal places
delaynumber—Delay before starting
durationnumber—Animation duration in seconds
isActiveboolean—Whether animation is active
prefixstring—Prefix (e.g., ”$“)
separatorboolean—Use thousands separator
styleCSSProperties—Additional styles
suffixstring—Suffix (e.g., ”%“)

<MotionPresence>

import { MotionPresence } from "@getnarro/core";

MotionPresence - Wrapper for AnimatePresence with slide-aware animations

PropTypeRequiredDescription
childrenReactNodeyesChildren to animate
mode"sync" | "wait" | "popLayout"—Animation mode
presenceKeystring—Custom key for tracking

<MotionSpotlight>

import { MotionSpotlight } from "@getnarro/core";

Apple Keynote-style Magic Move for images Creates smooth zoom and pan animations between slides. Uses Framer Motion’s layout animations for seamless morphing.

Behaviour. Sizes itself from containerHeight and dims everything but the highlighted child; the children keep their own layout, only their opacity changes.

// Slide 1 - Overview
<MotionSpotlight
  layoutId="product"
  src="/product.jpg"
  alt="Product"
  x={50} y={50} scale={1}
  width={800} height={400}
/>

// Slide 2 - Zoomed
<MotionSpotlight
  layoutId="product"
  src="/product.jpg"
  alt="Product detail"
  x={25} y={50} scale={2.5}
  width={800} height={400}
/>
PropTypeRequiredDescription
altstringyesAlt text for accessibility
srcstringyesImage source URL
borderRadiusstring | number—Border radius
classNamestring—Additional class names
durationnumber—Animation duration in seconds
heightstring | number—Container height
isActiveboolean—Whether currently active
layoutIdstring—Unique ID for cross-slide morphing
onAnimationComplete(() => void)—Callback when animation completes
scalenumber—Scale/zoom level
shadowboolean—Show shadow
widthstring | number—Container width
xnumber—X position as percentage (0-100)
ynumber—Y position as percentage (0-100)

<MotionStep>

import { MotionStep } from "@getnarro/core";

Individual step within MotionSteps container Shows when the current step is >= this step’s number.

<MotionStep step={1} effect="scaleUp">
  <div>This appears on step 1</div>
</MotionStep>
PropTypeRequiredDescription
childrenReactNodeyesChildren to animate
stepnumberyesStep number (0-indexed) - element shows when currentStep >= this value
classNamestring—Additional class name
durationnumber—Animation duration in seconds
easingMotionEasing—Easing function
effectMotionEffect—Animation effect when revealed
styleCSSProperties—Additional styles

<MotionSteps>

import { MotionSteps } from "@getnarro/core";

Container for step-by-step animations triggered by keystrokes Wrap MotionStep children to create a sequence of animations. Press arrow keys (right/down) to advance, (left/up) to go back.

<MotionSteps totalSteps={3}>
  <MotionStep step={0} effect="scaleUp">
    <div>First element (visible immediately)</div>
  </MotionStep>
  <MotionStep step={1} effect="slideRight">
    <div>Second element (press arrow key)</div>
  </MotionStep>
  <MotionStep step={2} effect="elastic">
    <div>Third element (press again)</div>
  </MotionStep>
</MotionSteps>
PropTypeRequiredDescription
childrenReactNodeyesChildren - should be MotionStep components
classNamestring—Additional class name
loopboolean—Whether to loop back to start after last step
onComplete(() => void)—Callback when all steps are complete
startStepnumber—Starting step (0-indexed)
totalStepsnumber—Total number of steps

<MotionText>

import { MotionText } from "@getnarro/core";

Animated text with various effects

Behaviour. Animates per word or per character, which means the text is split into inline elements — a text-* utility on the parent still applies, but a selector expecting one text node will not match.

<MotionText type="typewriter" duration={0.05}>
  Hello, World!
</MotionText>
PropTypeRequiredDescription
childrenstringyesText content
classNamestring—Additional class names
delaynumber—Delay before starting in seconds
durationnumber—Animation duration per unit in seconds
isActiveboolean—Whether animation is active
styleCSSProperties—Additional styles
type"blur" | "typewriter" | "wave" | "wordByWord" | "charByChar"—Animation type

<MotionTransform>

import { MotionTransform } from "@getnarro/core";

Applies cumulative transforms to a single element across steps Use this inside MotionSteps to animate the SAME element through multiple transformation states (fade, zoom, move, rotate, etc.)

<MotionSteps totalSteps={4}>
  <MotionTransform
    transforms={[
      { opacity: 0 },                           // Step 0 (hidden)
      { opacity: 1 },                           // Step 1 (fade in)
      { opacity: 1, scale: 1.5 },              // Step 2 (zoom)
      { opacity: 1, scale: 1.5, x: 150 },      // Step 3 (move right)
    ]}
  >
    <div className="w-64 h-64 bg-blue-500">
      Transform me!
    </div>
  </MotionTransform>
</MotionSteps>
PropTypeRequiredDescription
childrenReactNodeyesChildren to transform
transformsTransformState[]yesArray of transform states for each step
classNamestring—Additional class name
durationnumber—Animation duration in seconds
easingMotionEasing—Easing function
styleCSSProperties—Additional styles

<NebulaBackground>

import { NebulaBackground } from "@getnarro/core";

An animated starfield with nebula glow, behind whatever you put in it.

Behaviour. Fills its positioning parent and sits behind the content. Give the slide position: relative (or put it directly in a Slide) or it will escape to the nearest positioned ancestor.

<NebulaBackground isActive>
  <Heading level={1}>Title over the stars</Heading>
</NebulaBackground>
PropTypeRequiredDescription
backgroundGradientstring—Background gradient (CSS value)
childrenReactNode—Children to render on top of the background
classNamestring—Additional CSS class for the container
isActiveboolean—Whether the animation is active
nebulaColorsNebulaGlow[]—Nebula glow effects configuration
shootingStarDurationnumber—Duration of shooting star animation in ms (default: 2000)
shootingStarFrequencynumber—Time between shooting stars in ms (default: 3000)
starColorsStarColor[]—Star color distribution
starLayersStarLayer[]—Star layer configuration
twinkleSpeed{ min: number; max: number; }—Twinkle speed range in ms

<Notes>

import { Notes } from "@getnarro/core";

Notes component - Speaker notes Notes are not rendered in the main presentation view. They are meant to be displayed in presenter mode or exported separately.

<Slide>
  <h1>My Slide Title</h1>
  <Notes>
    Remember to explain the key points here.
    - Point 1
    - Point 2
  </Notes>
</Slide>
PropTypeRequiredDescription
childrenReactNodeyesSpeaker notes content

<OverviewGrid>

import { OverviewGrid } from "@getnarro/core";

OverviewGrid component - Displays slides in a grid for quick navigation

Behaviour. A full-screen overlay, not an in-slide element. Presentation renders it on O; you do not place it yourself.

PropTypeRequiredDescription
actionsNavigationActionsyesNavigation actions
slidesDataSlideData[]yesSlides data for rendering previews
stateNavigationStateyesNavigation state

<Presentation>

import { Presentation } from "@getnarro/core";

Presentation component - Root container

<Presentation>
  <Slide id="intro">
    <h1>Welcome</h1>
  </Slide>
  <Slide id="content">
    <h2>Main Content</h2>
  </Slide>
</Presentation>
PropTypeRequiredDescription
childrenReactNodeyesChildren slides
aspectRatioAspectRatio—Aspect ratio (default: "16:9")
autoPlaynumber | boolean | number[]—Enable auto-play mode to automatically advance slides. Can be a boolean (uses default 5000ms interval) or a number specifying the interval in milliseconds. Can also be an array of numbers specifying the interval for each slide.
autoPlayLoopboolean—Whether to loop back to the first slide when auto-play reaches the end. Default: false
baseHeightnumber—Base height for viewport scaling (default: 1080). Content scales proportionally from this baseline
baseWidthnumber—Base width for viewport scaling (default: 1920). Content scales proportionally from this baseline
classNamestring—Additional CSS classes
embeddedboolean—Enable embedded mode for presentations in smaller containers. When true, viewport scaling uses container size instead of window size. Useful for embedding presentations in modals, cards, or side panels.
enableViewportScalingboolean—Enable viewport scaling (default: true). Scales content based on viewport size for responsive presentations
faviconstring | null—Favicon URL or data URI for the presentation. Can be a path to an image file, a data URI, or undefined to use the default Narro favicon. Set to null to disable favicon management (keep existing favicon).
initialSlidenumber—Initial slide index
keepAwakeboolean—Keep the screen awake while the presentation is open (Screen Wake Lock API, best-effort). Useful so the projector/laptop never sleeps mid-talk. Default: false
keyboardboolean—Enable keyboard navigation (default: true)
maxDurationnumber—Optional maximum duration in minutes for countdown timer in presenter panel
mouseboolean—Enable mouse navigation (default: true)
onAutoPlayComplete(() => void)—Callback when auto-play reaches the last slide and completes. Only called when autoPlay is enabled.
onSlideChange((index: number) => void)—Callback when slide changes
onSlidesData((slides: SlideData[]) => void)—Callback invoked with the registered slide data whenever the slide set changes. Useful for building companion views (notes, overviews) that need access to slide content and speaker notes.
preserveAspectRatioboolean—Preserve aspect ratio (default: true). When false, slides fill viewport
routingboolean—Enable URL-based routing (default: true)
showAutoPlayIndicatorboolean—Show an auto-play indicator icon at the bottom right of the presentation. Shows a play/pause icon during playback and a replay icon when complete. Clicking the replay icon restarts the presentation. Default: false
syncPresentationSync—Broadcast the current slide to a presenter-sync relay so companion views (e.g. a speaker-notes window on a different port) can follow along live. Pass true to use the default relay URL, or an object to customise it. Default: false
themePresentationTheme—Theme configuration for the presentation
touchboolean—Enable touch navigation (default: true)
transformTransformConfig—Transform-based animation configuration

<PresenterNotes>

import { PresenterNotes } from "@getnarro/core";

PresenterNotes component - live speaker-notes companion view.

PropTypeRequiredDescription
childrenReactNodeyesThe same slide elements rendered by the deck. Their inline <Notes> are harvested and shown in slide order.
classNamestring—Additional CSS classes for the root container.
currentSlidenumber—Controlled current slide index. When provided, sync is ignored and the view follows this value (useful for tests or custom wiring).
keepAwakeboolean—Keep the screen awake while the notes view is open (best-effort). Default: true (this is a presenter tool).
pacingPresenterPacing—Optional pacing schedule. When provided, the notes view shows an elapsed timer plus an on-time / ahead / over indicator and the planned total.
script((slide: { id: string; index: number; step: number; }) => string | undefined)—Optional resolver for a fuller, per-slide script (e.g. a verbatim manuscript) shown in a secondary, scrollable pane next to the notes. Return undefined for slides that have no script. When omitted, no script pane is shown.
syncPresentationSync—Subscribe to a live deck via the presenter-sync relay. Pass true for the default relay URL or an object to customise it.

<Slide>

import { Slide } from "@getnarro/core";

Slide component - Individual slide wrapper

<Slide id="intro" layout="title">
  <h1>Welcome to My Presentation</h1>
  <p>Subtitle goes here</p>
</Slide>
// With sub-slides
<Slide id="multi-step" subSlides={[
{ id: "step1", content: <div>Step 1</div> },
{ id: "step2", content: <div>Step 2</div>, transition: "slide-left" }
]} />
PropTypeRequiredDescription
__slideIndexnumber—Internal: Explicit slide index from Presentation component
backgroundstring—Background color or image
childrenReactNode—Children content
classNamestring—Additional CSS classes
idstring—Unique identifier for the slide
layoutLayoutType—Layout type (default: “default”)
subSlidesSubSlide[]—Sub-slides for multi-step content within this slide
transitionTransitionEffect—Transition effect

<SlideContent>

import { SlideContent } from "@getnarro/core";

SlideContent component - Content layout container

Behaviour. Fills the slide (h-full w-full). Every layout stacks its children vertically. default sets nothing else, so content anchors to the top and the rest of the canvas stays empty — for a content slide you usually want fill (a column, so a child with flex-1 takes the remaining space) or between. className overrides any of it.

<Slide>
  <SlideContent layout="centered">
    <h1>Centered Content</h1>
  </SlideContent>
</Slide>
PropTypeRequiredDescription
childrenReactNodeyesChildren content
classNamestring—Additional CSS classes
layout"default" | "centered" | "top" | "bottom" | "fill" | "between"—How the slide’s content is arranged in the canvas. All of these stack vertically. centered used to be a flex row with no flexDirection, so the canonical title-slide example in the scaffolded AGENTS.md — a Heading and a Text — rendered side by side, and the fit checker passed it because it did not overflow, it was just wrong. - default — content stacks from the top. Note this leaves the rest of the canvas empty; fill or between is usually what a content slide wants. - centered — centred on both axes. For title and section slides. - top / bottom — anchored, horizontally centred. - fill — a column that occupies the canvas, so a child with flex-1 takes the remaining space. This is “title on top, body fills the rest”. - between — a column with the space distributed between children.

<TransformSlide>

import { TransformSlide } from "@getnarro/core";

A slide with multiple sub-slides and transform-based navigation

Behaviour. Owns the whole slide and pans a larger canvas within it, so it cannot share a slide with ordinary content — its sub-slide positions are coordinates on that canvas.

PropTypeRequiredDescription
subSlidesTransformSubSlide[]yesArray of sub-slides with their positions
backgroundstring—Background color or gradient
classNamestring—Additional CSS classes
durationnumber—Camera flight time in seconds, like every other @getnarro/core duration (shared-ui uses milliseconds; see the Limitations page). 1.2 is a second and a bit, not a millisecond and a bit. A value above {@link IMPLAUSIBLE_DURATION_SECONDS} is warned about in development, because it is almost always milliseconds typed into a seconds prop — which renders as a camera that never appears to move.
easing"linear" | "easeIn" | "easeOut" | "easeInOut" | "circInOut" | "backInOut"—Transition easing (default: “easeInOut”)
initialSubSlidenumber—Initial sub-slide index (default: 0)
perspectiveboolean—Render the plane in 3D so position.z reads as depth. Off by default: a perspective context promotes the camera subtree to a GPU texture, which is what makes scaled text blurry, and z is rarely used.
perspectiveValuenumber—Perspective value in pixels (default: 1000)

<TrustedByMarquee>

import { TrustedByMarquee } from "@getnarro/core";

Rows of logos or names scrolling infinitely in alternating directions.

Behaviour. A full-width column of continuously scrolling rows, sized by its content. It animates forever, so it is a background rather than something to place beside content.

<TrustedByMarquee
  rows={[[{ name: "Acme", color: "#ffffff" }], [{ name: "Globex", color: "#60a5fa" }]]}
  isActive
  baseSpeed={40}
/>
PropTypeRequiredDescription
rowsMarqueeItem[][]yesArray of row data, each containing items
baseSpeednumber—Base scroll speed (default: 0.3)
classNamestring—Additional CSS class for the container
gapnumber—Gap between items in pixels (default: 16)
isActiveboolean—Whether the animation is active
itemMinWidthnumber—Item minimum width in pixels (default: 180)
renderItem((item: MarqueeItem, index: number) => ReactNode)—Custom item render function
rowGapnumber—Gap between rows (default: 8)
speedIncrementnumber—Speed increment per row (default: 0.05)

@getnarro/replay

One component, imported from @getnarro/replay.

<Replay>

import { Replay } from "@getnarro/replay";

A recording of a real web app, in a page. Plays a cast — DOM, not video — so it stays sharp at any size and its text stays selectable.

Behaviour. Takes the width available and the height that follows from the recording’s own aspect ratio, the way an <img> does. It scales rather than reflows: the page was laid out once, at the viewport it was recorded at, and every scroll offset in the recording assumes that width. Pass fill for a box the parent has already sized — a slide region — where it letterboxes into whatever shape that box has.

PropTypeRequiredDescription
srcstringyesThe cast to play, as a URL — an asset the app serves, like an image.
autoplayboolean—
chapterstring | number—A chapter name or a time in milliseconds. Changing it seeks — which is how an app drives a recording: one step, one chapter.
classNamestring—
controlsboolean—Draw the transport bar. Off by default on a slide, where the deck drives.
cursor"pointer" | "trail" | "none"—pointer shows the cursor, trail also draws its path, none hides it.
fillboolean—Fill the box the parent gives, instead of taking a height from the recording’s own shape. What a deck slide wants; a page usually does not.
lazyboolean—Load the player and the cast only once the replay is near the viewport. Worth it for a demo below the fold on a landing page. Wrong for one on a slide that is in the DOM but hidden: an element inside display: none never intersects anything, so it would never load. Off by default.
loopboolean—
onFinish(() => void)—Fires when the recording reaches its end. A looping replay never ends.
onReady((detail: { duration: number; }) => void)—Fires once the recording has loaded and drawn its first frame.
onTime((detail: { at: number; duration: number; }) => void)—Fires as the recording plays, and once on every seek. What anything drawn over a replay needs: a caption that appears at four seconds, a chapter list that marks where the viewer has got to, a step that advances with the recording rather than with a click. Without it a page has to poll currentTime on a frame loop of its own, beside the one the player is already running. The element’s own event is timeupdate and carries nothing, because <narro-terminal> emits the same one and a page driving both wants one handler. Here the two numbers are read off the element and passed, because a React caller who had to reach for a ref to learn the time would be holding two ways of addressing the same player. It fires at frame rate while playing, so keep the handler cheap — set state only when the value it derives has actually changed.
posterstring—A still, shown until the recording can draw, and left standing if the player never loads. It is the whole of the server-rendered output, and what narro export --pdf and --pptx print. narro record --poster demo.png writes one.
refRef<ReplayHandle>—The transport, as a ref. React 19 and later — on React 18, drive the element directly through @getnarro/replay/element.
speednumber—
startstring | number—Where to open, if the recording should not start at the beginning.
styleCSSProperties—

@getnarro/shared-ui

54 components, imported from @getnarro/shared-ui.

<AgendaItem>

import { AgendaItem } from "@getnarro/shared-ui";

A styled agenda item component Displays an agenda item with a centered number in bold with a colored underline, followed by descriptive text. Useful for agenda presentations and step-by-step processes.

<AgendaItem number={1} title="Introduction" />
<AgendaItem number={2} title="Main Topic" underlineColor="#00BFA5" />
PropTypeRequiredDescription
numbernumberyesThe number of the agenda item
titlestringyesTitle of the agenda item
childrenReactNode—Children content
classNamestring—Additional CSS classes
underlineColorstring—Color of the underline

<AnimatedList>

import { AnimatedList } from "@getnarro/shared-ui";

List with staggered animations Note: Import the animations CSS file to use this component: import “@getnarro/shared-ui/styles/animations.css”;

Behaviour. A vertical column of its items with staggered entry; it does not inherit the surrounding list styling, and it is one element per item rather than a <ul>.

<AnimatedList
  items={['First point', 'Second point', 'Third point']}
  animation="slide-up"
  staggerDelay={150}
  variant="check"
/>
PropTypeRequiredDescription
itemsReactNode[]yesList items to animate
animationListAnimationType—Animation type
classNamestring—Additional CSS classes
durationnumber—Animation duration per item in milliseconds
initialDelaynumber—Delay before starting first animation in milliseconds
isActiveboolean—Override slide active state
orderedboolean—Ordered or unordered list
staggerDelaynumber—Delay between each item in milliseconds
variant"disc" | "decimal" | "check" | "arrow" | "none"—List style variant

<Animation>

import { Animation } from "@getnarro/shared-ui";

A CSS animation wrapper component When used inside a Slide component from narro, animations will only trigger when the slide becomes active. Note: Import the animations CSS file to use this component: import “@getnarro/shared-ui/styles/animations.css”;

<Animation type="slide-up" delay={200} duration={500}>
  <Heading level={1}>Animated Title</Heading>
</Animation>
PropTypeRequiredDescription
childrenReactNode—Children content
classNamestring—Additional CSS classes
delaynumber—Animation delay (ms)
durationnumber—Animation duration (ms)
easing"linear" | "ease-out" | "ease-in-out" | "ease" | "ease-in"—Animation easing
isActiveboolean—Override slide active state (for use without narro context)
trigger"mount" | "hover" | "visible"—Trigger animation on mount
typeAnimationType—Animation type

<Badge>

import { Badge } from "@getnarro/shared-ui";

a short inline label.

<Badge tone="success">WCAG AA</Badge>
PropTypeRequiredDescription
childrenReactNode—Children content
classNamestring—Additional CSS classes
radiusSurfaceRadius—Corner rounding. Defaults to a pill.
toneSurfaceTone—Colour role of the badge.

<BigText>

import { BigText } from "@getnarro/shared-ui";

Extremely large, impactful text component

<BigText effect="gradient" animate="blur-reveal">
  IMPACT
</BigText>
PropTypeRequiredDescription
align"center" | "left" | "right"—Text alignment
animateBigTextAnimation—Animation preset
animationDurationnumber—Animation duration in milliseconds
childrenReactNode—Children content
classNamestring—Additional CSS classes
colorstring—Text color
effectBigTextEffect—Text effect to apply
gradientColorsstring—Custom gradient colors (when effect is “gradient”)

<Callout>

import { Callout } from "@getnarro/shared-ui";

a card with a coloured edge, for the one thing on the slide that matters most.

<Callout tone="warning" title="Not swapped">
  This one is added, not replaced.
</Callout>
PropTypeRequiredDescription
childrenReactNode—Children content
classNamestring—Additional CSS classes
iconReactNode—Anything to sit left of the text — an <Icon>, an emoji.
titlestring—Bold line above the body.
toneSurfaceTone—Colour role of the edge and background.

<Card>

import { Card } from "@getnarro/shared-ui";

a bordered surface.

<Card tone="primary" padding="lg">
  <Heading level={3}>Narro</Heading>
  <Text>Decks as code.</Text>
</Card>
PropTypeRequiredDescription
borderedboolean—Draw the border. Off gives a plain tinted panel.
childrenReactNode—Children content
classNamestring—Additional CSS classes
padding"none" | "sm" | "md" | "lg"—Inner spacing.
radiusSurfaceRadius—Corner rounding.
toneSurfaceTone—Colour role of the surface.

<Chart>

import { Chart } from "@getnarro/shared-ui";

A simple chart component Deliberately small: bar, line, pie and donut, drawn with CSS and SVG, for the one number a slide is about. There is no axis, no gridline, no tooltip and no stacking, and adding them is not the plan — a slide that needs a real chart should render it with a real charting library and place the image, which is a documented workflow rather than a shrug.

Behaviour. Bars are proportional to the largest value in this chart, unless max says otherwise. height is the whole component including the title and legend, and the plot is clamped to 80px, so give a bar chart at least 200px or the bars stop communicating. showValues reserves room for the value beside each bar.

<Chart
  type="bar"
  data={[
    { label: 'Q1', value: 100 },
    { label: 'Q2', value: 150 },
    { label: 'Q3', value: 200 },
  ]}
  title="Quarterly Sales"
/>
PropTypeRequiredDescription
dataChartDataPoint[]yesChart data
type"line" | "bar" | "pie" | "donut"yesChart type
classNamestring—Additional CSS classes
colorsstring[]—Colour palette, applied in data order. To emphasise one point regardless of ordering, set color on that ChartDataPoint instead — it wins over the palette.
heightnumber—Chart height
maxnumber—The value a full-length bar represents. Defaults to the largest value in this chart’s own data, which is what makes two charts on two slides quietly incomparable: each is drawn against its own maximum, so a 60-unit bar and a 90-unit bar can be the same length on consecutive slides and a reader comparing them gets a wrong answer with nothing to warn them. Pin it when the comparison spans slides. Not an axis, and not a step toward one — it is the one piece of scale information a slide can carry without this becoming a chart library.
orientation"horizontal" | "vertical"—Bar direction. Horizontal reads better when labels are long: a long label under a narrow column is unreadable, which is what vertical-only forced. Bar charts only.
showLegendboolean—Show the category legend. Defaults to on for pie/donut and off for everything else. It used to default to on for all of them, so a single-series bar chart printed the same three names under bars that already carried them — noise, and 40px of plot height gone.
showValuesboolean—Print each value on its bar, rather than folding it into the label.
titlestring—Chart title
unitstring—Unit appended to every printed value — unit="KB" prints 72 KB. A bundle-size chart needs “72 KB”, not “72”, and folding the unit into the label meant showValues then printed the bare number a second time. Declarative because an attribute block in a markdown deck cannot pass a function.
valueFormat((value: number) => string)—Full control over how a value is printed. Wins over {@link unit}.

<Code>

import { Code } from "@getnarro/shared-ui";

A code block component with optional line numbers and copy button

Behaviour. Nothing is tokenised here. language is recorded as a language-* class and used for the label; it does not colour anything. A markdown fence is highlighted — Narro tokenises it when the deck compiles — but this component takes its code as children at runtime, and a React deck has no compile step to do it in. Highlighting it would mean shipping a highlighter and its grammars to the browser, which for a runtime the docs already apologise for is a worse trade than grey code.

If the colour is load-bearing in a React deck, run Shiki’s codeToHtml at build time and drop the result in — it keys off the same language-* class. See Limitations.

<Code language="typescript" lineNumbers>
{`const greeting = "Hello, World!";
console.log(greeting);`}
</Code>
PropTypeRequiredDescription
childrenstringyesCode content
classNamestring—Additional CSS classes
copyButtonboolean—Show copy button
highlightLinesstring—Lines to highlight (comma-separated or range)
languagestring—Programming language
lineNumbersboolean—Show line numbers
preClassNamestring—Classes for the inner <pre>, which is what carries the type size. The size used to be an inline style, so className="text-[12px]" on the block did nothing and there was no way to shrink code to fit a slide. Both work now; this is the direct spelling.
theme"dark" | "light"—Theme variant

<CommentForm>

import { CommentForm } from "@getnarro/shared-ui";

A form for creating or editing comments

<CommentForm
  slideId="slide-1"
  defaultAuthor="John Doe"
  onSubmit={(comment) => handleAddComment(comment)}
  onCancel={() => setShowForm(false)}
/>
PropTypeRequiredDescription
onSubmit(comment: Omit<SlideComment, "id" | "createdAt">) => voidyesCallback when comment is submitted
slideIdstringyesSlide ID to add comment to
classNamestring—Additional CSS classes
defaultAuthorstring—Default author name
initialContentstring—Initial content (for editing)
isEditingboolean—Whether this is editing an existing comment
onCancel(() => void)—Callback when form is cancelled
parentIdstring—Parent comment ID for replies
placeholderstring—Placeholder text
positionCommentPosition—Position for inline comments

<CommentItem>

import { CommentItem } from "@getnarro/shared-ui";

Displays a single comment with actions

<CommentItem
  comment={comment}
  onEdit={(id) => handleEdit(id)}
  onDelete={(id) => handleDelete(id)}
  onResolve={(id, resolved) => handleResolve(id, resolved)}
/>
PropTypeRequiredDescription
commentSlideCommentyesThe comment to display
classNamestring—Additional CSS classes
currentUserstring—Current user’s name (to determine if they can edit/delete)
onDelete((commentId: string) => void)—Callback when delete is clicked
onEdit((commentId: string) => void)—Callback when edit is clicked
onReaction((commentId: string, emoji: string) => void)—Callback when a reaction is added
onReply((commentId: string, content: string) => void)—Callback when reply is submitted
onResolve((commentId: string, resolved: boolean) => void)—Callback when resolve is toggled
repliesSlideComment[]—Child comments (replies)
showReplyFormboolean—Whether to show the reply form

<CommentMarker>

import { CommentMarker } from "@getnarro/shared-ui";

A visual indicator for comment positions on slides

<CommentMarker
  position={{ x: 50, y: 30 }}
  count={3}
  onClick={() => setShowComments(true)}
/>
PropTypeRequiredDescription
positionCommentPositionyesPosition of the marker on the slide (percentage 0-100)
classNamestring—Additional CSS classes
commentIdstring—Comment ID this marker represents
countnumber—Number of comments at this position
isActiveboolean—Whether the marker is in an active/selected state
onClick(() => void)—Callback when marker is clicked

<CommentPanel>

import { CommentPanel } from "@getnarro/shared-ui";

A sidebar panel for managing comments

<CommentPanel
  slideId="slide-1"
  comments={allComments}
  onAddComment={(comment) => addComment(comment)}
  onDeleteComment={(id) => deleteComment(id)}
  currentUser="John Doe"
/>
PropTypeRequiredDescription
commentsSlideComment[]yesAll comments data
onAddComment(comment: Omit<SlideComment, "id" | "createdAt">) => voidyesCallback to add a new comment
classNamestring—Additional CSS classes
currentUserstring—Current user name
filterCommentFilterOptions—Filter options
isCollapsedboolean—Whether the panel is in collapsed state
onCollapseChange((collapsed: boolean) => void)—Callback when collapse state changes
onDeleteComment((commentId: string) => void)—Callback to delete a comment
onFilterChange((filter: CommentFilterOptions) => void)—Callback when filter changes
onResolveComment((commentId: string, resolved: boolean) => void)—Callback to resolve/unresolve a comment
onUpdateComment((commentId: string, updates: Partial<SlideComment>) => void)—Callback to update a comment
slideIdstring—Slide ID to show comments for (if undefined, shows all)

<ContentSlide>

import { ContentSlide } from "@getnarro/shared-ui";

A slide with optional title and content area

<ContentSlide title="Key Features">
  <List items={['Feature 1', 'Feature 2', 'Feature 3']} />
</ContentSlide>
PropTypeRequiredDescription
childrenReactNode—Children content
classNamestring—Additional CSS classes
titleReactNode—Slide title

<CountingNumber>

import { CountingNumber } from "@getnarro/shared-ui";

Animated number counter

<CountingNumber
  value={1000000}
  duration={2000}
  format={{ prefix: "$", separator: true }}
/>
PropTypeRequiredDescription
valuenumberyesTarget value to count to
classNamestring—Additional CSS classes
delaynumber—Delay before starting animation in milliseconds
durationnumber—Animation duration in milliseconds
easing"linear" | "ease-out" | "ease-in-out"—Animation easing
format{ decimals?: number | undefined; prefix?: string | undefined; suffix?: string | undefined; separator?: boolean | undefined; locale?: string | undefined; }—Number format options
fromnumber—Starting value (default: 0)
isActiveboolean—Override slide active state
onComplete(() => void)—Callback when animation completes

<Diagram>

import { Diagram } from "@getnarro/shared-ui";

a monospaced block for ASCII diagrams. children is ReactNode, not a string, so individual nodes of a tree can be coloured with a <span> inside the block — which is how “these components re-rendered and these did not” gets said without a drawing primitive. The type size lives on the inner <pre>: pass preClassName, or a text-* utility in className, which cn will resolve in your favour.

<Diagram preClassName="text-xl">
{`
  +--------+     +--------+
  | Client | --> | Server |
  +--------+     +--------+
`}
</Diagram>
// Individual nodes styled, because children are nodes rather than text.
<Diagram>
  {"Client\n  "}
  <span style={{ color: "#f43f5e" }}>{"Server (re-rendered)"}</span>
</Diagram>
PropTypeRequiredDescription
childrenReactNode—Children content
classNamestring—Additional CSS classes
preClassNamestring—Classes for the inner <pre>, which is what carries the type size. className lands on the wrapper, so sizing the diagram used to need [&_pre]:!text-xl — an arbitrary variant plus an !important, for the one thing every author changes. The <pre>’s own defaults are classes now, so either works; this is the direct spelling.
theme"dark" | "light" | "forest"—Diagram theme

<DottedArc>

import { DottedArc } from "@getnarro/shared-ui";

Decorative concentric dotted arcs Creates a series of concentric arcs with dashed lines in varying colors. Used as a decorative element for title slides and maintaining brand consistency.

<DottedArc
  colors={["#ff6b35", "#4ecdc4", "#1a535c"]}
  position="bottom-right"
/>
// Custom sizing and spacing
<DottedArc
  colors={["orange", "green", "blue"]}
  width={3}
  spacing={10}
  size={400}
  position="bottom-left"
/>
PropTypeRequiredDescription
colorsstring[]yesArray of colors for each arc
classNamestring—Additional CSS classes
position"bottom-right" | "bottom-left" | "top-right" | "top-left"—Arc position
sizenumber—Size of the arc container
spacingnumber—Spacing between arcs
widthnumber—Line width for arcs

<DualColumnTextAndImage>

import { DualColumnTextAndImage } from "@getnarro/shared-ui";

A two-column layout where text is on one side and an image is on the other

Behaviour. A full-height flex row: text in one column, image in the other, each taking the whole height. It expects to be the slide’s main content rather than to sit under a heading.

<DualColumnTextAndImage
  text="This is the text content"
  image="/path/to/image.png"
  imageAlt="Description of image"
  gap="md"
/>
PropTypeRequiredDescription
imagestringyesURL of the image to display in the right column
textReactNodeyesText content to display in the left column
classNamestring—Additional CSS classes
gap"sm" | "md" | "lg" | "xl"—Gap between columns
imageAltstring—Alt text for the image
reverseboolean—Reverse the layout (image left, text right)

<Embed>

import { Embed } from "@getnarro/shared-ui";

External content embedding component Supports YouTube, Vimeo, Google Slides, websites, web applications, and generic iframes.

// Embed a YouTube video
<Embed
  url="https://www.youtube.com/watch?v=dQw4w9WgXcQ"
  title="Video demonstration"
  aspectRatio="16:9"
/>

// Embed a web application with sandbox restrictions
<Embed
  url="https://codesandbox.io/embed/..."
  title="Live code demo"
  sandbox="allow-scripts allow-same-origin"
  loading="lazy"
/>

// Embed a website
<Embed
  url="https://example.com"
  title="Example website"
  aspectRatio="16:9"
/>
PropTypeRequiredDescription
urlstringyesURL to embed (YouTube, Vimeo, websites, web apps, etc.)
allowstring—Custom permissions for iframe (default includes common permissions)
allowFullscreenboolean—Allow fullscreen
aspectRatio"1:1" | "16:9" | "4:3"—Aspect ratio
classNamestring—Additional CSS classes
loading"lazy" | "eager"—Lazy loading strategy
referrerPolicy"no-referrer" | "no-referrer-when-downgrade" | "origin" | "origin-when-cross-origin" | "same-origin" | "strict-origin" | "strict-origin-when-cross-origin" | "unsafe-url"—Referrer policy
sandboxstring—Sandbox restrictions for iframe security
titlestring—Embed title for accessibility

<FlowDiagram>

import { FlowDiagram } from "@getnarro/shared-ui";

A visual representation of a process flow with steps

Behaviour. Lays its steps along a single axis chosen by direction, full width, with connectors between adjacent steps. It draws a chain — there is no branching or converging.

<FlowDiagram
  steps={[
    {label: 'Step 1', icon: '/icon1.png', description: 'First step'},
    {label: 'Step 2', icon: '/icon2.png', description: 'Second step'}
  ]}
  direction="horizontal"
  color="#08979c"
/>
PropTypeRequiredDescription
stepsFlowStep[]yesList of steps with labels and optional icons
classNamestring—Additional CSS classes
colorstring—Color for step boxes and connectors
direction"horizontal" | "vertical"—Direction of flow
gap"sm" | "md" | "lg" | "xl"—Gap between steps

<FourColumn>

import { FourColumn } from "@getnarro/shared-ui";

A four-column layout component with equal columns

<FourColumn
  first={<Text>Column 1</Text>}
  second={<Text>Column 2</Text>}
  third={<Text>Column 3</Text>}
  fourth={<Text>Column 4</Text>}
  gap="lg"
  align="center"
/>
PropTypeRequiredDescription
firstReactNodeyesFirst column content
fourthReactNodeyesFourth column content
secondReactNodeyesSecond column content
thirdReactNodeyesThird column content
align"center" | "start" | "end" | "stretch"—Vertical alignment
classNamestring—Additional CSS classes
gap"sm" | "md" | "lg" | "xl"—Gap between columns
responsiveboolean—Stack columns on small screens

<Fragment>

import { Fragment } from "@getnarro/shared-ui";

Step-by-step reveal component Each Fragment animates in sequence based on its order prop. The delay is calculated as: order * staggerDelay When used inside a Slide component from narro, animations will only trigger when the slide becomes active. Note: Import the animations CSS file to use this component: import “@getnarro/shared-ui/styles/animations.css”;

<Fragment order={1} effect="fade-in">
  <Text>This appears first (after 400ms)</Text>
</Fragment>
<Fragment order={2} effect="slide-up">
  <Text>This appears second (after 800ms)</Text>
</Fragment>
PropTypeRequiredDescription
childrenReactNode—Children content
classNamestring—Additional CSS classes
durationnumber—Animation duration (ms)
effectFragmentEffect—Animation effect
isActiveboolean—Override slide active state (for use without narro context)
ordernumber—Order in which fragment appears
staggerDelaynumber—Delay between each fragment (ms) - used to stagger animations
trigger"time" | "step"—What reveals the fragment. "step" (the default) waits for the presenter: each press reveals the next fragment and the press after the last one advances the slide. "time" reveals everything on slide entry, staggered by order — the behaviour before reveals were wired to navigation. Outside a Slide there is nothing to step, so "time" is used whatever this says.

<Grid>

import { Grid } from "@getnarro/shared-ui";

A flexible grid layout component

<Grid columns={3} gap="md">
  <InfoCardWithIcon title="First">Item 1</InfoCardWithIcon>
  <InfoCardWithIcon title="Second">Item 2</InfoCardWithIcon>
  <InfoCardWithIcon title="Third">Item 3</InfoCardWithIcon>
</Grid>
PropTypeRequiredDescription
align"center" | "start" | "end" | "stretch"—Horizontal alignment
childrenReactNode—Children content
classNamestring—Additional CSS classes
columns2 | 3 | 4 | 5 | 6 | 1—Number of columns
gap"sm" | "md" | "lg" | "xl"—Gap between items
justify"center" | "start" | "end" | "stretch"—Vertical alignment

<Heading>

import { Heading } from "@getnarro/shared-ui";

A styled heading component (h1-h6)

Behaviour. level picks both the tag and the type scale, and the scale is a class — so className="text-[13rem]" replaces it rather than losing to it. Margin is zeroed; add your own with className.

<Heading level={1}>Main Title</Heading>
<Heading level={2} color="primary">Subtitle</Heading>
PropTypeRequiredDescription
align"center" | "left" | "right"—Text alignment
childrenReactNode—Children content
classNamestring—Additional CSS classes
color"default" | "primary" | "muted" | "accent"—Color variant
level2 | 3 | 4 | 5 | 6 | 1—Heading level (1-6)

<HexPatternBackground>

import { HexPatternBackground } from "@getnarro/shared-ui";

A background with a hexagonal pattern

Behaviour. Fills its positioning parent and sits behind the content, like every background. It sets no size of its own, so an unpositioned parent leaves it covering the viewport.

<HexPatternBackground backgroundColor="#00d9c4" hexColor="#08979c">
  <h1>Content here</h1>
</HexPatternBackground>
PropTypeRequiredDescription
backgroundColorstring—Background color (hex or rgb)
childrenReactNode—Content to be rendered on top of the hexagon pattern background
classNamestring—Additional CSS classes
hexColorstring—Hexagon stroke color (hex or rgb)
patternSize"sm" | "md" | "lg"—Pattern size (affects hexagon scale)

<Icon>

import { Icon } from "@getnarro/shared-ui";

A simple icon component A small closed set of typographic symbols — see ICON_NAMES. It is not a logo set and does not aim to be: an icon library that resolves names at runtime cannot be tree-shaken, so it would ship every glyph to every deck, on a runtime this library’s own documentation already apologises for. For a brand mark, use Image with the logo file. github used to be in the set, drawn as ⌘ — the Command key — which is a wrong answer rather than a rough one.

Behaviour. A name outside the set draws nothing and reports to the console. It used to render the name as text, so <Icon name="rocket" /> put the word “rocket” on the slide. TypeScript catches this now; the runtime guard is for a name computed at runtime, and it reports as an error so narro check --fit — which fails a deck whose page logged one — catches it too.

<Icon name="check" size="lg" color="green" />
<Icon name="terminal" size={32} />
PropTypeRequiredDescription
name"check" | "arrow" | "code" | "link" | "search" | "cross" | "arrow-left" | "arrow-up" | "arrow-down" | "star" | "heart" | "warning" | "info" | "question" | "plus" | "minus" | "terminal" | "email" | "phone" | "location" | "calendar" | "clock" | "file" | "folder" | "settings" | "user" | "users"yesWhich icon to draw. A closed set — see {@link ICON_NAMES}. This was string, documented as “Icon name or SVG content”. SVG content never worked: the value is rendered as text, so markup arrived on the slide escaped.
classNamestring—Additional CSS classes
colorstring—Icon color
labelstring—Accessibility label
sizenumber | "sm" | "md" | "lg" | "xl"—Icon size

<Image>

import { Image } from "@getnarro/shared-ui";

An optimized image component with filter support

<Image
  src="/images/photo.jpg"
  alt="A beautiful landscape"
  fit="cover"
  rounded="lg"
  shadow
  filter="grayscale"
/>
// Custom filter:
<Image
  src="/images/photo.jpg"
  alt="Custom filtered"
  customFilter="brightness(80%) contrast(110%)"
/>
// Color overlay:
<Image
  src="/images/photo.jpg"
  alt="Blue tinted"
  overlay="#3b82f6"
  overlayOpacity={0.4}
/>
PropTypeRequiredDescription
altstringyesAlt text for accessibility
srcstringyesImage source URL
childrenReactNode—Children content
classNamestring—Additional CSS classes
customFilterstring—Custom CSS filter value (overrides filter preset)
filterImageFilter—Apply CSS filter preset
fit"none" | "cover" | "contain" | "fill"—Object fit mode
heightstring | number—Height constraint
overlaystring—Color overlay (CSS color value)
overlayOpacitynumber—Opacity of the color overlay (0-1, default: 0.5)
rounded"none" | "sm" | "md" | "lg" | "full"—Border radius
shadowboolean—Add shadow
widthstring | number—Width constraint

<ImageBackground>

import { ImageBackground } from "@getnarro/shared-ui";

A full-bleed background image with content overlay

<ImageBackground
  src="/images/hero.jpg"
  alt="Hero background"
  overlay="dark"
  kenBurns
>
  <Heading level={1}>Bold Title</Heading>
  <Text>Subtitle text here</Text>
</ImageBackground>
PropTypeRequiredDescription
srcstringyesImage source URL
altstring—Image alt text for accessibility
childrenReactNode—Children content
classNamestring—Additional CSS classes
kenBurnsboolean—Enable Ken Burns animation effect (slow zoom/pan)
kenBurnsDurationnumber—Ken Burns animation duration in seconds
overlayImageBackgroundOverlay—Overlay type for text readability
overlayColorstring—Custom overlay color (CSS color value)
position"center" | "cover" | "contain" | "top" | "bottom"—Image positioning

<ImageSlide>

import { ImageSlide } from "@getnarro/shared-ui";

A full-bleed image slide with optional overlay

<ImageSlide
  src="/images/hero.jpg"
  alt="Hero image"
  overlay={<Heading level={1}>Welcome</Heading>}
  overlayPosition="center"
/>
PropTypeRequiredDescription
srcstringyesImage source URL
altstring—Image alt text
classNamestring—Additional CSS classes
fit"cover" | "contain" | "fill"—Object fit mode
overlayReactNode—Overlay content
overlayPosition"center" | "top" | "bottom"—Overlay position

<ImageSpotlight>

import { ImageSpotlight } from "@getnarro/shared-ui";

Keynote-style Magic Move for images Creates smooth zoom and pan animations between slides. Elements with the same spotlightId will animate smoothly when transitioning between slides.

// Slide 1 - Overview (centered, no zoom)
<ImageSpotlight
  spotlightId="diagram"
  src="/architecture.svg"
  alt="Architecture diagram"
  x={50}
  y={50}
  scale={1}
  width="100%"
  height={400}
/>

// Slide 2 - Zoom to top-left element
<ImageSpotlight
  spotlightId="diagram"
  src="/architecture.svg"
  alt="Architecture diagram"
  x={25}
  y={25}
  scale={2.5}
  width="100%"
  height={400}
/>

// Slide 3 - Pan to bottom-right element
<ImageSpotlight
  spotlightId="diagram"
  src="/architecture.svg"
  alt="Architecture diagram"
  x={75}
  y={75}
  scale={2.5}
  width="100%"
  height={400}
/>
PropTypeRequiredDescription
altstringyesAlt text for accessibility
spotlightIdstringyesUnique identifier for cross-slide morphing - elements with same id will animate between slides
srcstringyesImage source URL
classNamestring—Additional CSS classes for container
durationnumber—Animation duration in ms, default 800
easing"linear" | "ease-out" | "ease-in-out" | "ease" | "ease-in" | "spring"—Animation easing function
heightstring | number—Container height (CSS value or number in px)
imageClassNamestring—Additional CSS classes for image
onAnimationComplete(() => void)—Callback when animation completes
overlayReactNode—Optional overlay content that appears on top
rounded"none" | "sm" | "md" | "lg" | "xl" | "full"—Border radius
scalenumber—Scale/zoom level, default 1
shadowboolean—Show shadow
widthstring | number—Container width (CSS value or number in px)
xnumber—X position as percentage (0-100), default 50 (centered)
ynumber—Y position as percentage (0-100), default 50 (centered)

<InfoCardWithIcon>

import { InfoCardWithIcon } from "@getnarro/shared-ui";

A styled information card with icon Displays an icon on the left with bold headings and descriptive text to the right. Uses consistent alignment and spacing. Perfect for concept highlights and important points.

<InfoCardWithIcon
  iconSrc="/icon.svg"
  title="Key Feature"
  description="This is an important concept to highlight"
/>
PropTypeRequiredDescription
descriptionstringyesDescription text explaining the icon
iconSrcstringyesURL for the icon image
titlestringyesTitle of the info card
childrenReactNode—Children content
classNamestring—Additional CSS classes

<List>

import { List } from "@getnarro/shared-ui";

An animated list component

<List
  items={['First point', 'Second point', 'Third point']}
  variant="check"
  animated
/>
PropTypeRequiredDescription
itemsReactNode[]yesList items
animatedboolean—Enable animations
animationDelaynumber—Animation delay between items (ms)
classNamestring—Additional CSS classes
orderedboolean—Ordered or unordered list
variant"disc" | "decimal" | "check" | "arrow" | "none"—List style

<LogoGrid>

import { LogoGrid } from "@getnarro/shared-ui";

A grid layout for displaying multiple logos with even spacing

Behaviour. A CSS grid of exactly columns equal columns, full width, each cell centring its logo. Rows wrap as needed; there is no way to size an individual cell. An entry with no src is set as a wordmark, so a text-only row of names is one call rather than a Grid of Cards.

<LogoGrid
  logos={[
    {src: '/path/to/logo1.png', name: 'Company 1'},
    {src: '/path/to/logo2.png', name: 'Company 2'}
  ]}
  columns={3}
  gap="md"
/>
PropTypeRequiredDescription
logosLogo[]yesArray of logos. src is optional: an entry without one is set as a wordmark, so a row of product or company names needs no image files. Mixing the two is fine.
classNamestring—Additional CSS classes
columns2 | 3 | 4 | 5 | 6—Number of columns
gap"sm" | "md" | "lg" | "xl"—Gap between logos

<MorphElement>

import { MorphElement } from "@getnarro/shared-ui";

Element that morphs across slides using View Transitions API When elements with the same transitionName exist on consecutive slides, they will smoothly morph into each other during slide transitions.

// Slide 1
<MorphElement transitionName="hero-image">
  <img src="product.jpg" style={{ width: 200 }} />
</MorphElement>

// Slide 2 - same transitionName, different size
<MorphElement transitionName="hero-image">
  <img src="product.jpg" style={{ width: "100%" }} />
</MorphElement>
PropTypeRequiredDescription
childrenReactNodeyesElement content
transitionNamestringyesUnique name for cross-slide morphing
asElementType<any, keyof IntrinsicElements>—HTML tag to render
classNamestring—Additional CSS classes
styleCSSProperties—Additional inline styles

<ProcessFlowChart>

import { ProcessFlowChart } from "@getnarro/shared-ui";

Interactive flowchart for process visualization Creates a flowchart with customizable nodes and connections. Nodes are automatically laid out based on the direction prop, and connections are drawn between them with arrows.

Behaviour. Draws a chain, not a graph: nodes sit in one line in array order along direction, and connections only chooses which arrows appear between those fixed positions. A fan-in, a branch or a cycle will not lay out — use Canvas with CanvasElement, or an SVG placed with Image. Node size is 220×90 by default; the chart sizes itself to the nodes.

<ProcessFlowChart
  nodes={[
    { id: "start", label: "Start", color: "#4ecdc4" },
    { id: "process", label: "Process", color: "#ff6b35" },
    { id: "end", label: "End", color: "#95e1d3" }
  ]}
  connections={[
    { from: "start", to: "process" },
    { from: "process", to: "end" }
  ]}
  direction="horizontal"
/>
// Vertical flow with custom styling
<ProcessFlowChart
  nodes={[
    { id: "1", label: "Input", color: "#1a535c", textColor: "#fff" },
    { id: "2", label: "Transform", color: "#4ecdc4" },
    { id: "3", label: "Output", color: "#ff6b35", textColor: "#fff" }
  ]}
  connections={[
    { from: "1", to: "2", label: "data" },
    { from: "2", to: "3", label: "result" }
  ]}
  direction="vertical"
  nodeGap="lg"
  arrowStyle="dashed"
/>
PropTypeRequiredDescription
connectionsProcessFlowConnection[]yesWhich pairs of nodes get an arrow drawn between them. This does not affect layout. Nodes sit in a single line in array order, along the axis direction chooses; a connection draws a line between two of those fixed positions and can label it. nodes plus connections reads like a graph API and is not one — a fan-in, a branch or a cycle will render as a straight line with arrows doubling back along it. For those, use Canvas with CanvasElement, or draw an SVG and place it with Image.
nodesProcessFlowNode[]yesArray of nodes with labels and colors. Laid out in the order given.
arrowStyle"solid" | "dashed"—Arrow style for connections
classNamestring—Additional CSS classes
direction"horizontal" | "vertical"—Orientation of the flow
nodeGap"sm" | "md" | "lg"—Gap between nodes
nodeHeightnumber—Node height in pixels. Defaults to 90, for the same reason.
nodeWidthnumber—Node width in pixels. Defaults to 220. It was a hardcoded 120, which on the 1920×1080 canvas a deck actually renders at is a postage stamp.

<ProfileCard>

import { ProfileCard } from "@getnarro/shared-ui";

A styled profile card component Displays a profile image, name, title, and associated company icons. Perfect for team introductions and speaker introductions.

<ProfileCard
  imageSrc="/avatar.jpg"
  name="John Doe"
  title="CEO & Founder"
  companyIcons={["/company1.svg", "/company2.svg"]}
/>
PropTypeRequiredDescription
imageSrcstringyesSource URL for the profile image
namestringyesName of the individual
titlestringyesTitle or role of the individual
childrenReactNode—Children content
classNamestring—Additional CSS classes
companyIconsstring[]—Array of icon URLs to display

<ProgressRing>

import { ProgressRing } from "@getnarro/shared-ui";

Animated circular progress indicator

Behaviour. A square of size pixels — both axes — laid out inline. The label sits at the centre of the ring rather than under it.

<ProgressRing value={75} size={120} showValue />
PropTypeRequiredDescription
valuenumberyesProgress value (0-100)
backgroundColorstring—Background ring color
childrenReactNode—Custom center content
classNamestring—Additional CSS classes
colorstring—Ring color
delaynumber—Delay before starting animation in milliseconds
durationnumber—Animation duration in milliseconds
isActiveboolean—Override slide active state
onComplete(() => void)—Callback when animation completes
showValueboolean—Show percentage text in center
sizenumber—Size of the ring in pixels
strokeWidthnumber—Stroke width in pixels

<Quote>

import { Quote } from "@getnarro/shared-ui";

A styled blockquote component

<Quote
  author="Albert Einstein"
  source="Interview, 1929"
>
  "Imagination is more important than knowledge."
</Quote>
PropTypeRequiredDescription
authorstring—Quote author
authorStyleCSSProperties—Custom style for the author/figcaption
childrenReactNode—Children content
classNamestring—Additional CSS classes
sourcestring—Source or reference
variant"default" | "large" | "minimal"—Visual variant

<Reveal>

import { Reveal } from "@getnarro/shared-ui";

Fragment - Step-by-step reveal component Each Fragment animates in sequence based on its order prop. The delay is calculated as: order * staggerDelay When used inside a Slide component from narro, animations will only trigger when the slide becomes active. Note: Import the animations CSS file to use this component: import “@getnarro/shared-ui/styles/animations.css”;

<Fragment order={1} effect="fade-in">
  <Text>This appears first (after 400ms)</Text>
</Fragment>
<Fragment order={2} effect="slide-up">
  <Text>This appears second (after 800ms)</Text>
</Fragment>

<ScrollableImage>

import { ScrollableImage } from "@getnarro/shared-ui";

Displays a scrollable image Perfect for displaying full-page website screenshots that are too tall to fit in a slide.

// Basic scrollable image
<ScrollableImage
  src="/screenshots/long-page.png"
  alt="Full page screenshot"
  maxHeight="70vh"
/>

// With auto-scroll
<ScrollableImage
  src="/screenshots/website.png"
  alt="Website screenshot"
  maxHeight="70vh"
  autoScrollSpeed={50}
  showIndicators
/>
PropTypeRequiredDescription
altstringyesAlt text for accessibility
srcstringyesImage source URL or path
autoScrollSpeednumber—Auto-scroll speed in pixels per second (0 to disable)
classNamestring—Additional CSS classes
maxHeightstring—Maximum height of the container
showIndicatorsboolean—Show scroll indicators
smoothScrollboolean—Enable smooth scrolling

<Text>

import { Text } from "@getnarro/shared-ui";

A styled text block component

Behaviour. Renders a <p> with no margin, so spacing between blocks is yours to set — className="mt-16" works. Size and weight are classes, so className overrides them.

<Text size="lg" weight="medium">
  This is styled body text for your slides.
</Text>
PropTypeRequiredDescription
align"center" | "left" | "right" | "justify"—Text alignment
childrenReactNode—Children content
classNamestring—Additional CSS classes
color"default" | "primary" | "muted" | "accent"—Color variant
size"sm" | "md" | "lg" | "xl" | "2xl"—Text size
weight"medium" | "normal" | "semibold" | "bold"—Font weight

<ThreeColumn>

import { ThreeColumn } from "@getnarro/shared-ui";

A three-column layout component

<ThreeColumn
  first={<Text>First column</Text>}
  second={<Text>Second column</Text>}
  third={<Image src="image.jpg" alt="Third column" />}
  ratio="1:2:1"
  gap="lg"
/>
PropTypeRequiredDescription
firstReactNodeyesFirst column content
secondReactNodeyesSecond column content
thirdReactNodeyesThird column content
align"center" | "start" | "end" | "stretch"—Vertical alignment
classNamestring—Additional CSS classes
gap"sm" | "md" | "lg" | "xl"—Gap between columns
ratio"1:1:1" | "2:1:1" | "1:2:1" | "1:1:2" | "2:2:1" | "1:2:2"—Column ratio (e.g., "1:1:1", "2:1:1", "1:2:1")
responsiveboolean—Stack columns on small screens

<TimelineWithIndicators>

import { TimelineWithIndicators } from "@getnarro/shared-ui";

A horizontal timeline component Creates a horizontal timeline with circular indicators linked by a line. Includes labels below each indicator. Perfect for historical overviews and progress tracking.

<TimelineWithIndicators
  events={[
    { year: "2020", label: "Founded" },
    { year: "2021", label: "Series A" },
    { year: "2022", label: "Expansion" },
  ]}
  lineColor="#14b8a6"
/>
PropTypeRequiredDescription
eventsTimelineEvent[]yesArray of event objects with year and label
classNamestring—Additional CSS classes
lineColorstring—Color of the connecting line

<TitleSlide>

import { TitleSlide } from "@getnarro/shared-ui";

A centered title slide layout

<TitleSlide
  title="Welcome to My Presentation"
  subtitle="An Overview of Our Progress"
  author="John Doe"
  date="January 2025"
/>
PropTypeRequiredDescription
titleReactNodeyesMain title
authorReactNode—Author or presenter name
classNamestring—Additional CSS classes
dateReactNode—Date or additional info
subtitleReactNode—Subtitle

<TitleSlideWithElement>

import { TitleSlideWithElement } from "@getnarro/shared-ui";

A title slide with a flexible element area

<TitleSlideWithElement
  title="Product Launch"
  subtitle="Introducing our newest innovation"
  element={<Image src="product.png" alt="Product" />}
  elementPosition="right"
  elementAnimation="fade-in"
/>
PropTypeRequiredDescription
elementReactNodeyesElement to display (image, icon, chart, etc.)
titleReactNodeyesMain title
animationDelaynumber—Animation delay in milliseconds
classNamestring—Additional CSS classes
elementAnimationElementAnimation—Animation preset for the element
elementPositionElementPosition—Position of the element relative to title/subtitle
subtitleReactNode—Subtitle

<TwoColumn>

import { TwoColumn } from "@getnarro/shared-ui";

A two-column layout component

Behaviour. A CSS grid with p-8 of its own padding. It sizes to its content unless you pass fill, which makes it take the parent’s full height — under a heading, fill needs <SlideContent layout="fill"> above it, or the columns take the whole canvas plus the heading and overflow.

<TwoColumn
  left={<Text>Left content</Text>}
  right={<Image src="image.jpg" alt="Right image" />}
  ratio="2:1"
/>
PropTypeRequiredDescription
leftReactNodeyesLeft column content
rightReactNodeyesRight column content
align"center" | "start" | "end" | "stretch"—Vertical alignment
classNamestring—Additional CSS classes
fillboolean—Take the full height of the parent. Off by default. It used to be unconditional height: 100%, which made the most ordinary slide shape there is — a title with a two-column body — overflow by exactly the height of the title: three slides of one deck overflowed by an identical 43px, which is the signature of a structural bug rather than of anyone’s content. Pair it with <SlideContent layout="fill"> for “title on top, columns take the rest”: the column box then fills what the heading left, rather than the whole canvas plus the heading.
gap"sm" | "md" | "lg" | "xl"—Gap between columns
ratio"1:1" | "2:1" | "1:2" | "3:2" | "2:3"—Column ratio

<TwoColumnHighlight>

import { TwoColumnHighlight } from "@getnarro/shared-ui";

Two-column layout with highlighted sections A layout component featuring two columns with different backgrounds, typically used for section headers or emphasizing key points. The left column is typically darker with a narrower width.

<TwoColumnHighlight
  leftContent={<Heading level={1}>Section Title</Heading>}
  rightContent={<Text>Description text goes here</Text>}
  ratio="1:2"
/>
// With custom colors
<TwoColumnHighlight
  leftContent="Key Point"
  rightContent="Detailed explanation"
  leftBg="#000000"
  rightBg="#ffffff"
  leftTextColor="#ffffff"
  rightTextColor="#000000"
  highlightColor="#ff6b35"
/>
PropTypeRequiredDescription
leftContentReactNodeyesContent for the left column
rightContentReactNodeyesContent for the right column
classNamestring—Additional CSS classes
highlightstring—Text to highlight in the right column
highlightColorstring—Highlight color for emphasized text
leftBgstring—Left column background color
leftTextColorstring—Left column text color
ratio"1:2" | "2:3" | "1:3"—Column width ratio, left:right
rightBgstring—Right column background color
rightTextColorstring—Right column text color

<TypewriterText>

import { TypewriterText } from "@getnarro/shared-ui";

Text that appears character by character

Behaviour. Reserves no space for the text it has not typed yet, so the surrounding layout shifts as it types. Give it a fixed-height parent when that matters. The cursor is 1em tall and part of the line box.

<TypewriterText speed="medium" cursor>
  Hello, World!
</TypewriterText>
PropTypeRequiredDescription
childrenstringyesText content to animate
classNamestring—Additional CSS classes
cursorboolean—Show blinking cursor
delaynumber—Delay before starting animation in milliseconds
isActiveboolean—Override slide active state
onComplete(() => void)—Callback when animation completes
speednumber | "slow" | "medium" | "fast"—Speed of typing in milliseconds per character

<Video>

import { Video } from "@getnarro/shared-ui";

A video embedding component

<Video
  src="/videos/demo.mp4"
  poster="/images/poster.jpg"
  title="Product demo video"
  autoPlay
  loop
  muted
/>
PropTypeRequiredDescription
srcstringyesVideo source URL
autoPlayboolean—Autoplay video
classNamestring—Additional CSS classes
controlsboolean—Show controls
fit"cover" | "contain" | "fill"—Object fit mode
loopboolean—Loop video
mutedboolean—Mute video
posterstring—Poster image
titlestring—Accessible title/description for the video

<WaveLine>

import { WaveLine } from "@getnarro/shared-ui";

A decorative curved dashed line component Creates a smooth, curved dashed line that spans across a slide to add dynamic visual elements. Perfect for title slide decoration and section separators.

<WaveLine />
<WaveLine color="#00BFA5" dashWidth={3} />
PropTypeRequiredDescription
childrenReactNode—Children content
classNamestring—Additional CSS classes
colorstring—Color of the dashed line
dashWidthnumber—Width of each dash

<WordReveal>

import { WordReveal } from "@getnarro/shared-ui";

Text that reveals word by word Note: Import the animations CSS file to use this component: import “@getnarro/shared-ui/styles/animations.css”;

<WordReveal animation="blur" staggerDelay={100}>
  This text will appear word by word
</WordReveal>
PropTypeRequiredDescription
childrenstringyesText content to animate (will be split by words)
animationWordRevealAnimation—Animation type
classNamestring—Additional CSS classes
delaynumber—Delay before starting animation in milliseconds
durationnumber—Animation duration per word in milliseconds
isActiveboolean—Override slide active state
onComplete(() => void)—Callback when all words are revealed
staggerDelaynumber—Delay between each word in milliseconds

<Workflow>

import { Workflow } from "@getnarro/shared-ui";

Animated workflow diagram Note: Import the animations CSS file to use this component: import “@getnarro/shared-ui/styles/animations.css”;

Behaviour. Lays its steps along the axis direction chooses, with fixed-height connectors between them. The step circles are a fixed 48px regardless of the canvas.

<Workflow animation="progressive" direction="horizontal">
  <WorkflowStep icon="1" title="Start">Begin here</WorkflowStep>
  <WorkflowStep icon="2" title="Process">Do the work</WorkflowStep>
  <WorkflowStep icon="3" title="Complete">All done!</WorkflowStep>
</Workflow>
PropTypeRequiredDescription
childrenReactNodeyesWorkflowStep children
animationWorkflowAnimation—Animation type
classNamestring—Additional CSS classes
connectorColorstring—Connector line color
directionWorkflowDirection—Layout direction
highlightActiveboolean—Highlight the currently active step
isActiveboolean—Override slide active state
showConnectorsboolean—Show connecting lines/arrows between steps
staggerDelaynumber—Delay between each step in milliseconds

<WorkflowStep>

import { WorkflowStep } from "@getnarro/shared-ui";

Individual step in a workflow

Behaviour. One step of a Workflow, with a fixed 48px marker. Used on its own it still renders the marker and its own layout, but no connectors.

PropTypeRequiredDescription
childrenReactNodeyesStep content
classNamestring—Additional CSS classes
descriptionstring—Step description
iconReactNode—Icon or number to display
titlestring—Step title

<ZoomImage>

import { ZoomImage } from "@getnarro/shared-ui";

Image with click-to-zoom functionality

Behaviour. Takes its height when given one and its natural size otherwise, so a zoom inside a fixed-height parent needs an explicit height or it will grow the parent.

<ZoomImage
  src="/diagram.png"
  alt="Architecture diagram"
  zoomable
  zoomScale={2}
/>
PropTypeRequiredDescription
altstringyesAlt text for accessibility
srcstringyesImage source URL
classNamestring—Additional CSS classes
fit"none" | "cover" | "contain" | "fill"—Object fit mode
heightstring | number—Height constraint
rounded"none" | "sm" | "md" | "lg" | "full"—Border radius
shadowboolean—Add shadow
widthstring | number—Width constraint
zoomableboolean—Enable click-to-zoom
zoomScalenumber—Zoom scale factor
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?
Zooming canvasHow do I zoom and pan across one big canvas instead of cutting between slides?
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?
VideoHow do I turn a story into a video, and keep it right when the words, the product or the source changes?
Recording a web appHow do I put a demo of my product in a deck or on a landing page without shipping a video of it?
Embedding a replayHow do I embed a recorded demo of my app in my React app, my Astro site, or any other page?
TerminalHow do I put a terminal demo in a deck, a README or a video, and find out when the tool it demonstrates stops saying what the demo says?
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?