> For the complete documentation index, see [llms.txt](https://analog.groupthought.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://analog.groupthought.com/reference/alpine/scroll-stagger.md).

# x-scroll-stagger

Directives for staggered scroll animations: a Motion timeline over a set of items, scrubbed by scroll progress.

Directives for staggered scroll animations: a Motion timeline over a set of items, scrubbed by scroll progress.

| Directive                                                      | What it does                                                                                           |
| -------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ |
| `x-scroll-stagger`                                             | The root. Builds a staggered timeline from its items and ties it to scroll progress                    |
| `x-scroll-stagger="{ selector: '[data-item]', stagger: 0.1 }"` | Options object; see [Options](#options). Extra keys are keyframes held while the root is fully visible |
| `x-scroll-stagger:enter`                                       | Motion keyframes each item plays while the root scrolls into view                                      |
| `x-scroll-stagger:leave`                                       | Motion keyframes each item plays while the root scrolls out                                            |

The timeline scrubs with scroll instead of running on a clock, so scrolling back rewinds the animation. Items are the root's descendants matching the `selector` option (default `[data-animate-stagger-item]`). A root taller than the viewport plays the enter sequence across its whole scroll range and skips the visible and leave phases.

## Examples

### A basic staggered entrance

Use `x-scroll-stagger:enter` with keyframe pairs to animate each item from its first value to its second:

```html
<div
  x-scroll-stagger="{ selector: '[data-item]', stagger: 0.1 }"
  x-scroll-stagger:enter="{ opacity: [0, 1] }"
>
  <span data-item>First</span>
  <span data-item>Second</span>
</div>
```

The Text spotlight section animates this way: each word is an item, and `stagger: 0.1` starts a word when the previous one is 10% through its own animation.

### Animating the exit

Use `x-scroll-stagger:leave` to play a second staggered sequence as the root scrolls out of view:

```html
<div
  x-scroll-stagger
  x-scroll-stagger:enter="{ opacity: [0.1, 1.0] }"
  x-scroll-stagger:leave="{ opacity: [1.0, 0.1] }"
>
  …
</div>
```

Text spotlight's "Animate on" setting maps to these directives: "Enter" renders only `:enter`, "Exit" only `:leave`, "Enter and exit" renders both.

### Holding a style while visible

Use extra keys in the options object as the keyframes items hold between entering and leaving:

```html
<div
  x-scroll-stagger="{ opacity: [1, 1], color: ['inherit', 'inherit'] }"
  x-scroll-stagger:enter="{ opacity: [0, 1] }"
>
  …
</div>
```

With no extra keys, items hold the final values of the enter keyframes. That covers the common fade, so most usage never sets this.

### Driving the timeline from another element

Use the `target` and `scrollOffset` options when the scroll progress should come from a different element than the root:

```html
<div
  x-scroll-stagger="{
    target: $sectionEl.querySelector('section-content'),
    scrollOffset: ['25% 100%', '50% 0%'],
  }"
  x-scroll-stagger:enter="{ opacity: [0.1, 1.0] }"
>
  …
</div>
```

Text spotlight uses this in its sticky full-screen mode: the section pins while its content element keeps scrolling, so the content element has to drive the timeline. Offsets are Motion scroll offsets ("element edge" then "viewport edge"). See the [Motion scroll documentation](https://motion.dev/docs/scroll) for the format.

## Options

| Option          | What it does                                                                                                                |
| --------------- | --------------------------------------------------------------------------------------------------------------------------- |
| `selector`      | Selector for the items inside the root (default `[data-animate-stagger-item]`)                                              |
| `stagger`       | Overlap between item animations, 0–1. `0.1` starts each item when the previous one is 10% through (default `0.2`)           |
| `scrollOffset`  | Motion scroll offsets that bound the enter, visible, and leave phases (default `['25% 100%', '0% 50%', '0% 0%', '75% 0%']`) |
| `target`        | Element whose scroll progress updates the timeline (default: the root)                                                      |
| *any other key* | Keyframes held while the root is fully visible                                                                              |

In Analog the plugin loads lazily: sections that use it carry `data-lazy-alpine` and `data-lazy-alpine-module="@theme/scroll-stagger"`, so the module only downloads when the section approaches the viewport.
