πŸ§©β€‚Component Reference

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

72 min readView as markdown

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

@getnarro/core

<AnimatePresence>

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.

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

<Canvas>

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>

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:
```tsx
<CanvasElement x="100px" y="50px" width="200px" height="100px">
  <div>Positioned at 100px, 50px</div>
</CanvasElement>

| Prop | Type | Required | Description |
| --- | --- | --- | --- |
| `children` | `ReactNode` | yes | Children content |
| `x` | `PositionValue` | yes | X position (% or px) |
| `y` | `PositionValue` | yes | Y position (% or px) |
| `className` | `string` | β€” | Additional CSS classes |
| `height` | `PositionValue` | β€” | Height (% or px, optional) |
| `opacity` | `number` | β€” | Opacity (0-1) |
| `rotate` | `number` | β€” | Rotation in degrees |
| `width` | `PositionValue` | β€” | Width (% or px, optional) |
| `zIndex` | `number` | β€” | Z-index for layering |

### `<CanvasShape>`

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.

```tsx
<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>

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>

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>

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>

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>

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>

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>

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>

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>

MotionPresence - Wrapper for AnimatePresence with slide-aware animations

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

<MotionSpotlight>

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>

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>

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>

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>

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>

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>

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>

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>

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>

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>

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
```tsx
<Slide id="multi-step" subSlides={[
{ id: "step1", content: <div>Step 1</div> },
{ id: "step2", content: <div>Step 2</div>, transition: "slide-left" }
]} />

| Prop | Type | Required | Description |
| --- | --- | --- | --- |
| `__slideIndex` | `number` | β€” | Internal: Explicit slide index from Presentation component |
| `background` | `string` | β€” | Background color or image |
| `children` | `ReactNode` | β€” | Children content |
| `className` | `string` | β€” | Additional CSS classes |
| `id` | `string` | β€” | Unique identifier for the slide |
| `layout` | `LayoutType` | β€” | Layout type (default: "default") |
| `subSlides` | `SubSlide[]` | β€” | Sub-slides for multi-step content within this slide |
| `transition` | `TransitionEffect` | β€” | Transition effect |

### `<SlideContent>`

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.

```tsx
<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>

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β€”Transition duration in seconds (default: 1)
easing"linear" | "easeIn" | "easeOut" | "easeInOut" | "circInOut" | "backInOut"β€”Transition easing (default: β€œeaseInOut”)
initialSubSlidenumberβ€”Initial sub-slide index (default: 0)
perspectivebooleanβ€”Enable 3D perspective (default: true)
perspectiveValuenumberβ€”Perspective value in pixels (default: 1000)

<TrustedByMarquee>

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/shared-ui

<AgendaItem>

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>

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>

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>

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>

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>

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>

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>

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>

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>

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>

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>

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>

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>

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>

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>

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>

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>

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>

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>

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>

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>

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>

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>

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>

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>

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>

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:
```tsx
<Image
  src="/images/photo.jpg"
  alt="Custom filtered"
  customFilter="brightness(80%) contrast(110%)"
/>

```tsx
Color overlay:
```tsx
<Image
  src="/images/photo.jpg"
  alt="Blue tinted"
  overlay="#3b82f6"
  overlayOpacity={0.4}
/>

| Prop | Type | Required | Description |
| --- | --- | --- | --- |
| `alt` | `string` | yes | Alt text for accessibility |
| `src` | `string` | yes | Image source URL |
| `children` | `ReactNode` | β€” | Children content |
| `className` | `string` | β€” | Additional CSS classes |
| `customFilter` | `string` | β€” | Custom CSS filter value (overrides filter preset) |
| `filter` | `ImageFilter` | β€” | Apply CSS filter preset |
| `fit` | `"none" \| "cover" \| "contain" \| "fill"` | β€” | Object fit mode |
| `height` | `string \| number` | β€” | Height constraint |
| `overlay` | `string` | β€” | Color overlay (CSS color value) |
| `overlayOpacity` | `number` | β€” | Opacity of the color overlay (0-1, default: 0.5) |
| `rounded` | `"none" \| "sm" \| "md" \| "lg" \| "full"` | β€” | Border radius |
| `shadow` | `boolean` | β€” | Add shadow |
| `width` | `string \| number` | β€” | Width constraint |

### `<ImageBackground>`

A full-bleed background image with content overlay

```tsx
<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>

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>

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>

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>

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>

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>

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>

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>

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>

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>

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>

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>
import { Reveal } from "@getnarro/shared-ui";

<ScrollableImage>

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>

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>

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>

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>

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>

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>

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>

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>

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>

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>

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>

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>

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>

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>

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?
Transform ModeHow do I zoom and pan across one big canvas instead of cutting between slides?
Import & ExportHow do I get this deck out as PPTX, PDF, or one file I can email?
AI IntegrationHow do I wire an AI assistant up to Narro so it can write and build decks?
ThemingHow do I change the colours, fonts, and overall look of a deck?
Deck TemplatesHow do I define one house style with named layouts my slides can reference, like a PowerPoint master?
TroubleshootingSomething is wrong with my deck. What is it, and how do I fix it?
LimitationsWhat can't Narro do, and what do I do instead?