---
title: "Stagger"
description: "A transition group that enters and leaves children in sequence."
category: Effects
status: beta
since: 0.3.4
tags: [transition, animation, stagger]
syncStatus: reviewed
verified: true
---

## Usage

### Basic
:::TuffDemoWrapper{demo="StaggerStaggerDemo" code-lang="vue"}
---
code: |
  <script setup lang="ts">
  import { ref } from 'vue'

  const items = ref([
    { id: 'a', text: 'Alpha' },
    { id: 'b', text: 'Beta' },
    { id: 'c', text: 'Gamma' },
  ])
  </script>

  <template>
    <TxStagger tag="div" :delay-step="30" :duration="180">
      <div v-for="item in items" :key="item.id">{{ item.text }}</div>
    </TxStagger>
  </template>
---
:::

### List Transition

```vue
<template>
  <TxStagger tag="ul" name="tx-stagger" :delay-base="40" :delay-step="24">
    <li v-for="notification in notifications" :key="notification.id">
      {{ notification.title }}
    </li>
  </TxStagger>
</template>
```

### Custom Transition Name
Your own transition CSS computes the per-item delay from `--tx-stagger-index`.

```vue
<template>
  <TxStagger name="fade-list" :duration="240" easing="linear">
    <article v-for="card in cards" :key="card.id">
      {{ card.title }}
    </article>
  </TxStagger>
</template>
```

```css
.fade-list-enter-active,
.fade-list-leave-active {
  transition: opacity 240ms linear;
  transition-delay: calc(var(--tx-stagger-index) * 24ms);
}
```

### Best Practices

- Always give children stable keys; without them the sequence is unpredictable.
- Keep `delayStep` small for long lists; large delays make later items look stuck.
- Use `tag="ul"` with `li` children for semantic lists instead of styling a `div` as one.
- Don't wrap virtualized rows; DOM reuse and staggered transitions fight each other.
- Set a custom `name` only when you also provide matching transition CSS.

## API Reference

### Props

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `tag` | `string` | `'div'` | Root tag. |
| `appear` | `boolean` | `true` | Runs the appear transition on first mount. |
| `name` | `string` | `'tx-stagger'` | Transition class-name prefix. |
| `duration` | `number` | `180` | Enter and leave duration in ms. |
| `delayStep` | `number` | `24` | Extra delay per child index, in ms. |
| `delayBase` | `number` | `0` | Base delay before the index delay, in ms. |
| `easing` | `'ease' \| 'ease-in' \| 'ease-out' \| 'ease-in-out' \| 'linear'` | `'ease-out'` | Timing function of the built-in transition. |

### Slots

| Slot | Props | Description |
|------|------|-------------|
| `default` | - | Keyed children. |

### CSS Variables

| Variable | Source | Description |
|----------|--------|-------------|
| `--tx-stagger-index` | child index | Set on each child for the delay calculation. |
| `--tx-stagger-duration` | `duration` | Transition duration. |
| `--tx-stagger-delay-step` | `delayStep` | Delay multiplier per child. |
| `--tx-stagger-delay-base` | `delayBase` | Base delay. |
| `--tx-stagger-easing` | `easing` | Timing function. |

## Overview

- It renders a Vue `TransitionGroup` with class `tx-stagger` and the root tag from `tag`, and adds no semantic role.
- Slot children are numbered in order after fragments (such as `v-for`) are flattened and comments dropped; each child's own inline style is kept.
- The built-in transition uses opacity and `translateY(6px)`, delaying each item by `delayBase + index × delayStep`.
- `name` and `appear` reach `TransitionGroup` from the first render, so the initial appear transition runs.

## Technologies

- Each child is cloned with `cloneVNode` to add `--tx-stagger-index`; the timing props are written on the root as CSS variables.
- Source: `packages/tuffex/packages/components/src/stagger/`.

<TuffDocSourceLink />
