---
title: "Transition"
description: "Transition wrappers for keyed content, lists, and drill-in pages."
category: Effects
status: beta
since: 0.3.4
tags: [transition, motion, list, auto-size, navigation]
syncStatus: reviewed
verified: true
---

## Usage

### Content Switch
One keyed child switches when its key changes; `preset` picks the motion, and `smooth-size` also eases the container's size.
:::TuffDemoWrapper{demo="TransitionTransitionContentDemo" code-lang="vue"}
---
code: |
  <template>
    <TxTransition :preset="preset" :duration="220" mode="out-in">
      <div :key="value">Panel {{ value }}</div>
    </TxTransition>

    <TxTransitionFade :duration="180" mode="out-in">
      <div :key="`fade-${value}`">Fade {{ value }}</div>
    </TxTransitionFade>

    <TxTransitionSlideFade :duration="180" mode="out-in">
      <div :key="`slide-${value}`">SlideFade {{ value }}</div>
    </TxTransitionSlideFade>

    <TxTransitionRebound :duration="200" mode="out-in">
      <div :key="`rebound-${value}`">Rebound {{ value }}</div>
    </TxTransitionRebound>
  </template>
---
:::

### List Add and Remove
`group` switches to `TransitionGroup`; every item needs a stable key.
:::TuffDemoWrapper{demo="TransitionTransitionListDemo" code-lang="vue"}
---
code: |
  <template>
    <TxTransition preset="slide-fade" group tag="div" :duration="180">
      <div v-for="item in items" :key="item.id">
        {{ item.text }}
      </div>
    </TxTransition>
  </template>
---
:::

### Page Push
Use `TxTransitionPush` for hierarchical navigation: pass `direction="forward"` going in and `"back"` coming out; changing the child's key turns the page.
:::TuffDemoWrapper{demo="TransitionTransitionPushDemo" code-lang="vue"}
---
code: |
  <script setup lang="ts">
  import { computed, ref } from 'vue'

  const pages = [
    { id: 'actions', title: 'Actions', rows: ['Copy path', 'Show in Finder', 'Send to…'] },
    { id: 'targets', title: 'Choose a target', rows: ['Clipboard history', 'System info', 'Quick note', 'Translate', 'Screenshot markup'] },
    { id: 'confirm', title: 'Confirm', rows: ['Allow once', 'Always allow'] },
  ]

  const depth = ref(0)
  const direction = ref<'forward' | 'back'>('forward')
  const page = computed(() => pages[depth.value])

  function next() {
    direction.value = 'forward'
    depth.value = Math.min(depth.value + 1, pages.length - 1)
  }

  function back() {
    direction.value = 'back'
    depth.value = Math.max(depth.value - 1, 0)
  }
  </script>

  <template>
    <TxButton :disabled="depth === 0" @click="back">Back</TxButton>
    <TxButton :disabled="depth === pages.length - 1" @click="next">Next</TxButton>

    <TxTransitionPush :direction="direction">
      <ul :key="page.id">
        <li v-for="row in page.rows" :key="row">{{ row }}</li>
      </ul>
    </TxTransitionPush>
  </template>
---
:::

### Best Practices

- Use `mode="out-in"` for keyed panel switches, so the old content leaves before the new one enters.
- Sibling tab switches are not a push; keep them on a fade and reserve `TxTransitionPush` for hierarchy.
- Use `smooth-size` when content height changes; use `fade`, `slide-fade`, or `rebound` for lists and repeated rows.
- Key list items by stable business ids; never by temporary indexes in reorderable lists.
- Keep repeated motion short, and reserve `rebound` for small affordances where overshoot is intended.

## API Reference

### TxTransition

#### Props

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `preset` | `'fade' \| 'slide-fade' \| 'rebound' \| 'smooth-size'` | `'fade'` | Transition preset; `smooth-size` delegates to `TxTransitionSmoothSize` only when `group=false`. |
| `group` | `boolean` | `false` | Renders keyed lists with `TransitionGroup`. |
| `tag` | `string` | `'div'` | Root tag when `group=true`. |
| `appear` | `boolean` | `true` | Runs the enter motion on first render. |
| `mode` | `'in-out' \| 'out-in'` | `'out-in'` | `Transition` mode for a single child; ignored with `group`. |
| `duration` | `number` | `180` | Duration in ms, written to `--tx-transition-duration`. |
| `easing` | `string` | `'cubic-bezier(0.2, 0, 0, 1)'` | Timing function, written to `--tx-transition-easing`; `rebound` enters on its own spring curve. |

#### Slots

| Slot | Props | Description |
|------|-------|-------------|
| `default` | - | One keyed child, or several stably keyed children when `group=true`. |

### TxTransitionSmoothSize

#### Props

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `appear` | `boolean` | `true` | Runs the enter motion on first render. |
| `mode` | `'in-out' \| 'out-in'` | `'out-in'` | `Transition` mode for the keyed child inside `TxAutoSizer`. |
| `duration` | `number` | `220` | Duration shared by the size transition and the inner motion. |
| `easing` | `string` | `'cubic-bezier(0.2, 0, 0, 1)'` | Easing shared by size and content motion. |
| `width` | `boolean` | `false` | Animates width changes. |
| `height` | `boolean` | `true` | Animates height changes. |
| `motion` | `'fade' \| 'slide-fade' \| 'rebound'` | `'fade'` | Inner content motion while the size animates. |

### TxTransitionPush

#### Props

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `direction` | `'forward' \| 'back'` | `'forward'` | `forward` pushes the new page in from the inline end; `back` reverses it. Mirrored in RTL. |
| `duration` | `number` | `220` | Duration of the push and the height tween, in ms; `0` swaps at once. |
| `easing` | `string` | `'cubic-bezier(0.23, 1, 0.32, 1)'` | Shared by push, fade, and height; Web Animations reads it, so no CSS variables. |
| `height` | `boolean` | `true` | Tweens the container from the old page's height to the new one's, then returns to `auto`. |
| `appear` | `boolean` | `false` | Pushes the first page in on mount too, along `direction`. |

#### Events

| Event | Payload | Description |
|------|---------|-------------|
| `before-enter` | `(el: Element)` | Fires before the new page is inserted. |
| `after-enter` | `(el: Element)` | Fires once the new page has pushed in; not for an enter cut short by the next switch. |
| `after-leave` | `(el: Element)` | Fires once the old page leaves the DOM, including when a same-key page replaces it mid-push. |

#### Slots

| Slot | Props | Description |
|------|------|-------------|
| `default` | - | Exactly one keyed element; changing its key turns the page. |

### Semantic Components

| Component | Preset |
|-----------|--------|
| `TxTransitionFade` | `fade` |
| `TxTransitionSlideFade` | `slide-fade` |
| `TxTransitionRebound` | `rebound` |
| `TxTransitionSmoothSize` | size-aware wrapper with a configurable inner `motion` |
| `TxTransitionPush` | its own drill-in implementation, not a `preset` |

## Overview

- `class` and `style` merge onto the outer `.tx-transition`; other attrs, Vue transition listeners included, go to `Transition` / `TransitionGroup`, or to `TxAutoSizer` under `smooth-size`.
- `smooth-size` is single-child: it wraps the content in `TxAutoSizer`, animates height by default, and hides overflow while measuring; with `group=true` it behaves as `fade`.
- `TxTransitionPush` moves both pages at once: the old page is pinned in place as `position: absolute` and made `inert`, while the new page stays in flow and sets the height. Focus inside the old page is lost; the host moves it to the new page.
- While a push runs, the container is `overflow: clip`, not `hidden`; at rest nothing is clipped, so focus rings inside a page stay whole.
- An interrupted switch continues from where things are drawn; turning back to the same key mid-push slides that page back from where the old one stopped.
- Under reduced motion, every preset collapses to a near-instant duration without its offset or scale; `TxTransitionPush` crossfades in place over 120ms and lands the height at once.

## Technologies

- `TxTransitionPush` drives Web Animations from the JS hooks of `<Transition :css="false">`: pages animate `translate`, leaving their own `transform` alone, the container animates `height`, and a page cut short resumes from `getComputedTiming().progress`.
- Source: `packages/tuffex/packages/components/src/transition/`.

<TuffDocSourceLink />
