# Animation

> Fragments, transitions, and the motion primitives

## Fragments

A fragment reveals part of a slide on the next keypress instead of showing
everything at once.

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

<Slide id="reveal">
  <SlideContent>
    <Heading level={2}>Progressive reveal</Heading>

    <Fragment order={1}>
      <Text>Appears first</Text>
    </Fragment>

    <Fragment order={2} effect="slide-up">
      <Text>Appears second, sliding up</Text>
    </Fragment>

    <Fragment order={3} effect="scale-in" duration={400}>
      <Text>Then this</Text>
    </Fragment>
  </SlideContent>
</Slide>;
```

`Fragment` comes from `@getnarro/shared-ui`, not `@getnarro/core`.

| Prop | Type | Default |
| --- | --- | --- |
| `order` | `number` | source order |
| `effect` | `FragmentEffect` | `"fade-in"` |
| `duration` | `number` (ms) | — |
| `staggerDelay` | `number` (ms) | — |
| `isActive` | `boolean` | inherited from the slide |

`FragmentEffect` is <!-- generated:union:FragmentEffect -->"fade-in" | "fade-out" | "slide-up" | "slide-down" | "slide-left" | "slide-right" | "scale-in" | "scale-out" | "blur-in"<!-- /generated:union:FragmentEffect -->.

Fragments consume a navigation step: on a slide with three fragments, the first
three presses reveal them and the fourth advances. Every key that advances a
slide advances a fragment too, including the `PageDown` a presenter's clicker
sends.

Without an explicit `order`, fragments reveal in source order. Set `order` when
you want a different one — two fragments sharing an order reveal together.

`trigger="time"` opts out: those fragments reveal on slide entry, staggered by
`staggerDelay`, and do not consume a press — a slide holding only time-triggered
fragments advances on the first press, not on the *n*th.

Fragments without an explicit `order` are numbered in source order, and the
numbering depends only on what is mounted — so a slide costs the same number of
presses every time it is entered.

```tsx
<Fragment>revealed first</Fragment>
<Fragment>then this</Fragment>
<Fragment trigger="time">already there when the slide opens</Fragment>
```

Used outside a `Slide` — a component in a storybook, a page that is not a deck
— there is nothing to step, so a fragment reveals on mount.

In markdown mode the equivalent is `{.step}`, and it behaves the same way:

```markdown
- appears first {.step}
- appears second {.step delay=200}
```

## Slide transitions

```tsx
<Slide transition="fade">…</Slide>
```

`"none" | "fade" | "slide" | "zoom"`. In markdown, set `transition:` in slide
frontmatter, or on the deck to change the default for every slide.

## Motion primitives

`@getnarro/core` wraps Framer Motion in components that already know whether
their slide is on screen — animations start when the slide appears, not when the
deck mounts.

```tsx
import { Motion, MotionNumber, MotionText } from "@getnarro/core";

<Motion effect="slideUp" delay={0.2}>
  <Heading level={1}>Animated in</Heading>
</Motion>

<MotionText type="wordByWord">One word at a time</MotionText>

<MotionNumber value={42} suffix="%" decimals={0} />
```

**Units differ between the two families, and nothing warns.** Durations and
delays on `Motion*` components are in **seconds** (Framer Motion's convention);
everything in `shared-ui` — `Fragment.duration`, `TypewriterText.speed`,
`CountingNumber.duration` — is in **milliseconds**. `duration={2}` is two
seconds in one and two milliseconds in the other, and both are valid numbers.
The rule that always holds: **`core` is seconds, `shared-ui` is milliseconds.**

### Which of the two to reach for

`Fragment` and `MotionSteps`/`MotionStep` both stage a slide in pieces, and they
are not interchangeable — the difference is who owns the presenter's keypresses:

| | `Fragment` (`shared-ui`) | `MotionSteps` / `MotionStep` (`core`) |
| --- | --- | --- |
| Navigation | **Consumes a keypress per fragment.** The slide takes N presses to leave | **Consumes nothing.** It manages its own index and needs `totalSteps` |
| Units | Milliseconds | Seconds |
| Built on | CSS keyframes | Framer Motion |
| Reach for it when | The presenter should reveal the slide at their own pace — the common case | The sequence is a self-contained animation you want to replay or drive yourself |

**If you want "press space, the next thing appears", that is `Fragment`.**
Mixing both on one slide means two things claiming the same keypress, so pick
one per slide.

`MotionEffect` is <!-- generated:union:MotionEffect -->"fadeIn" | "fadeOut" | "slideUp" | "slideDown" | "slideLeft" | "slideRight" | "scaleUp" | "scaleDown" | "rotateIn" | "flip" | "bounce" | "elastic" | "blur" | "glow" | "typewriter" | "spotlight" | "parallax" | "morphPath" | "stagger" | "wave"<!-- /generated:union:MotionEffect -->.

See the [component reference](/docs/components) for the full set — `Motion`,
`MotionContainer`, `MotionList`, `MotionText`, `MotionNumber`,
`MotionPresence`, `MotionSpotlight`, `MotionSteps`, `MotionStep`,
`MotionTransform`.

## Gating your own animation

To drive animation yourself, ask whether the slide is active. Both packages
export the hook — `@getnarro/shared-ui` and `@getnarro/core` — and both read the
context `Slide` provides, so either import works:

```tsx
import { useSlideActive } from "@getnarro/shared-ui";

function Chart() {
  const isActive = useSlideActive();
  return <svg className={isActive ? "animate-draw" : "opacity-0"} />;
}
```

Without this, every animation on every slide fires on load and is finished
before the audience sees it.

## Respecting reduced motion

Motion components honour `prefers-reduced-motion`. If you hand-roll animation,
gate it the same way:

```css
@media (prefers-reduced-motion: reduce) {
  .animate-draw {
    animation: none;
  }
}
```