---
area: "Learn"
parent: "Motion chapter"
url: "https://unpoly.com/learn/transitions"
---
<nav aria-label="Unpoly docs">[All docs](https://unpoly.com/index.md) · [Motion chapter](https://unpoly.com/learn/motion.md)</nav>

# Transitions

A transition animates the swap of a fragment: the old element morphs into its new version. Unpoly ships with a few transitions that you can use by name. You can also combine two animations into a transition, or define your own in JavaScript.

<nav aria-label="Contents">

#### Contents

- [Using a transition](#transitions)
- [How a transition plays](#how-a-transition-plays)
- [Custom transitions](#custom-transitions)

</nav>

## Using a transition {#transitions}

To animate the swap of a fragment, set an [`[up-transition]`](https://unpoly.com/api/up-transition.md) attribute on a link:

```html
<a href="/stories/2" up-target="#story" up-transition="move-left">Next story</a>
```

When the response arrives, the old `#story` moves out to the left while the new one moves in from the right.

Transitions work on any fragment update. Forms take the same attribute:

```html
<form action="/tasks" up-target=".tasks" up-transition="cross-fade">
  ...
</form>
```

From JavaScript, pass a `{ transition }` option to [`up.render()`](https://unpoly.com/api/up.render.md) or any function that renders:

```js
up.render({ target: '#story', url: '/stories/2', transition: 'move-left' })
```

To use a transition for every [navigation](https://unpoly.com/learn/navigation-defaults.md), set a default in [`up.fragment.config.navigateOptions`](https://unpoly.com/api/up.fragment.config.md#config.navigateOptions):

```js
up.fragment.config.navigateOptions.transition = 'cross-fade'
```

When a [cached](https://unpoly.com/learn/caching.md#revalidation) page is revalidated, the fresh content replaces it without a second transition. Pages restored by the _Back_ button are not animated.

To change a transition's duration or easing, or to run code after it has ended, see [Controlling motion](https://unpoly.com/learn/controlling-motion.md).

### Available transitions {#available-transitions}

Unpoly ships with the following transitions, which you can use by name:

| Transition | Visual effect |
| --- | --- |
| `cross-fade` | Fades out the old element. Simultaneously fades in the new element. |
| `move-up` | Moves the old element upwards until it exits the screen at the top edge. Simultaneously moves the new element upwards from beyond the bottom edge of the screen until it reaches its current position. |
| `move-down` | Moves the old element downwards until it exits the screen at the bottom edge. Simultaneously moves the new element downwards from beyond the top edge of the screen until it reaches its current position. |
| `move-left` | Moves the old element leftwards until it exits the screen at the left edge. Simultaneously moves the new element leftwards from beyond the right edge of the screen until it reaches its current position. |
| `move-right` | Moves the old element rightwards until it exits the screen at the right edge. Simultaneously moves the new element rightwards from beyond the left edge of the screen until it reaches its current position. |
| `none` | Swaps the elements instantly, with no visible effect. |

The `none` transition is useful when a default transition is configured and a single link should update instantly. Setting `[up-transition=false]` has the same effect.

### Combining two animations into a transition {#combining-animations}

A transition is two animations playing at once: one on the old element, one on the new. To combine any two [animation names](https://unpoly.com/learn/animations.md#available-animations) into a transition, join them with a slash. The first animation plays on the old element, the second on the new:

```html
<a href="/stories/2" up-target="#story" up-transition="move-to-bottom/fade-in">Next story</a>
```

The predefined transitions are such combinations. For example, `move-left` is `move-to-left/move-from-right`.

### Transitions for error responses {#fail-transition}

An error response does not use [`[up-transition]`](https://unpoly.com/api/up-transition.md). When the server responds with an error code, Unpoly renders the response with [different options](https://unpoly.com/learn/error-responses.md#fail-options), and the form with validation errors appears instantly. To animate that case, set an `[up-fail-transition]` attribute:

```html
<form action="/tasks" up-target=".tasks" up-transition="cross-fade" up-fail-transition="move-down">
  ...
</form>
```

A default transition in [`up.fragment.config.navigateOptions`](https://unpoly.com/api/up.fragment.config.md#config.navigateOptions) applies to both cases.

## How a transition plays {#how-a-transition-plays}

During a transition both elements occupy the same spot on the screen:

- The new element is inserted before the old element.
- The old element is taken out of the document flow and positioned over its former place.
- The new element is compiled and scrolled into view.
- Both elements animate in parallel.
- The old element is removed from the DOM.

The new element takes the old one's place in the document flow, so elements after the fragment only shift when the new version has a different size.

While the transition plays, the old element is still in the DOM with an [`.up-destroying`](https://unpoly.com/api/up-destroying.md) class. Unpoly functions like [`up.fragment.get()`](https://unpoly.com/api/up.fragment.get.md) ignore it, but `document.querySelector()` may still find it.

A transition cannot replace the `<body>` element, which Unpoly never animates. To transition the entire page, target a container inside `<body>`.

## Custom transitions {#custom-transitions}

To define a named transition, pass a function to [`up.transition()`](https://unpoly.com/api/up.transition.md). The function receives the old element, the new element and the `{ duration, easing }` options, and returns a promise for the end of the transition. Here is the definition of the predefined `cross-fade` transition:

```js
up.transition('cross-fade', function(oldElement, newElement, options) {
  return Promise.all([
    up.animate(oldElement, 'fade-out', options),
    up.animate(newElement, 'fade-in', options)
  ])
})
```

The name is now available in [`[up-transition]`](https://unpoly.com/api/up-transition.md) attributes and `{ transition }` options.

### Honoring the caller's options {#custom-effect-contract}

Ending with [`up.animate()`](https://unpoly.com/api/up.animate.md) and passing along `options` keeps the caller's duration and easing, and lets [`up.motion.finish()`](https://unpoly.com/api/up.motion.finish.md) skip the transition to its last frame. Custom functions that animate another way learn about a finish request through the [`up:motion:finish`](https://unpoly.com/api/up:motion:finish.md) event. See [`up.transition()`](https://unpoly.com/api/up.transition.md) for the contract your function must honor.

<nav aria-label="Reading order">

- [Previous: Motion](https://unpoly.com/learn/motion.md)
- [Next: Animations](https://unpoly.com/learn/animations.md)

</nav>

