# <motion-swap>

Letter-swap text effect. Splits text into individual characters and
swaps them vertically on hover or when scrolled into view.

**Use it for:** short labels and headings whose letters should slide out
while copies slide in, when the mouse moves over them or when they scroll
into view (`trigger="view"`).

**Avoid it for:** long text, because every letter becomes two spans, and
text with links or other markup, which is removed. To move a whole element
on hover, use `motion-hover`.

**Accessibility:** screen readers read the whole text once from a visually
hidden copy, which also names a link or button the text sits in. The
letters, both the visible ones and the copies that swap in, are
`aria-hidden`. The hover trigger reacts to the mouse only, not to keyboard
focus.

**Reduced motion:** the letters never swap. The text stays still in its
normal state.

**Common mistakes:** changing the text or `trigger` after the first render:
both are read once, so replace the element instead. Setting `delay` with
the hover trigger and expecting it only on enter: the swap back on mouse
leave waits for `delay` too.

Docs: https://www.motion-components.dev/docs/text/motion-swap/

## Install

```js
import 'motion-components/motion-swap'
```

Or without a build step:

```html
<script type="module" src="https://cdn.jsdelivr.net/npm/motion-components@1/dist/motion-swap.js/+esm"></script>
```

## Example

```html
<motion-swap>Hello</motion-swap>
<motion-swap trigger="view">Hello</motion-swap>
```

## Attributes

| Attribute | Type | Default | Description |
| --- | --- | --- | --- |
| `trigger` | `'hover' \| 'view'` | `'hover'` | Trigger mode: `'hover'` (swap on mouseenter/mouseleave) or `'view'` (swap on viewport entry). |
| `reverse` | `boolean` | `true` | Letters swap bottom-to-top. Set `reverse="false"` to swap top-to-bottom. |
| `interval` | `number` | `0.03` | Delay in seconds between each character's animation start. |
| `duration` | `number` | `0.7` | Spring duration of each character swap, in seconds. |
| `bounce` | `number` | `0.3` | Spring bounciness (0 = critically damped, higher = more elastic). |
| `once` | `boolean` | `true` | When `true`, only animate the first time the element enters view (`trigger="view"`). Set `once="false"` to turn it off. Ignored with `loop`. |
| `loop` | `boolean` | `false` | Swap the words, hold, swap back, then repeat. With `trigger="view"` the cycle runs while on screen. |
| `hold` | `number` | `1.6` | With `loop`, seconds the swapped words stay in place before they swap back. |
| `gap` | `number` | `0.5` | With `loop`, seconds before the next swap. |
| `pause-on-hover` | `boolean` | `false` | Pause the loop while the pointer is over the text. |
| `delay` | `number` | `0` | Delay in seconds before the animation starts. |

## Properties

| Property | Type | Description |
| --- | --- | --- |
| `playState` | `'idle' \| 'running' \| 'paused' \| 'finished'` | Current state: `'idle'`, `'running'`, `'paused'` or `'finished'`. |
| `finished` | `Promise<void>` | Resolves when the current run settles. A new run replaces the promise. |

## Methods

| Method | Description |
| --- | --- |
| `replay()` | Resets and re-runs the swap animation. |
| `play()` | Starts the animation, or resumes it when paused. Resolves once the run settles. Under `prefers-reduced-motion: reduce` the final state is applied immediately without animating. |
| `pause()` | Holds the animation at its current position. No-op unless running. |
| `finish()` | Jumps to the final state and settles. Fires `motion-finish`. |
| `cancel()` | Reverts to the initial state and returns to idle. Fires `motion-cancel`. |

## Events

The `motion-*` events bubble and are composed.

| Event | Description |
| --- | --- |
| `motion-start` | When a run starts. |
| `motion-finish` | When a run finishes, or `finish()` jumps to the end. |
| `motion-cancel` | When `cancel()` stops a run and resets it. |

## Slots

| Slot | Description |
| --- | --- |
| (default) | The text to animate. Plain text only — read once on connect. |
