# Animate what your components already know

A dialog knows when it is open. A menu knows when it is closing. That state is already in the DOM as an attribute, and easeful is a CSS layer that animates it. No JavaScript ships to the browser, and the whole API is one attribute you add yourself.

## Getting started

Two steps, and the second one is the whole API.

1. **Import the layer once**

   Everything ships inside `@layer easeful`, deliberately low priority, so your own unlayered CSS overrides it without `!important`.

```css
/* Once, globally. If Tailwind v4 is present, the layer order
   must be declared before any import. */
@layer easeful, theme, base, components, utilities;
@import "easeful";
```

2. **Add the attribute to anything with open and closed state**

   There is no provider, no wrapper component, and nothing to wire up. The second half of the match, the state attribute, is already being written by Radix, Base UI, or the browser.

```tsx
<Dialog.Content data-motion="scale-fade" />
```

## The preset vocabulary

These 4 names are the entire surface. They are generated from the
manifest, so a value that typechecks is a value that exists. The
[attribute reference](/docs/reference) lists the two attributes and every token beside them.

| Preset | What it does | Composes with |
| --- | --- | --- |
| `fade` | Fades opacity only. Overlays, backdrops, and anything where movement would distract. | `slide-up` |
| `scale-fade` | Fades in while scaling up slightly. Default choice for dialogs, dropdowns, and popovers. | `slide-up` |
| `slide-up` | Fades in while sliding up from slightly below. Menus and content anchored above their trigger. | `fade`, `scale-fade` |
| `collapse` | Animates a panel between zero and its natural height. The preset for accordions and collapsibles, where fading alone leaves the layout jumping. Takes a single element child. | `fade` |

Compose two of them with a space:

```tsx
<Dialog.Content data-motion="fade slide-up" />
```

## Why the exit is the hard part

Entering is easy: the element mounts and you animate it. Leaving is not, because the element has
to survive long enough to be animated, and each host decides that differently.

Radix keeps a closing node mounted and waits for `animationend`. A transition-only
implementation shows no exit on Radix at all, because a transition never fires that event. Base
UI writes starting and ending style attributes instead and prefers transitions, because a
transition can be cancelled part way through rather than snapping back. Native elements need
`display` and `overlay` in the transition list under `allow-discrete`, or they leave the top
layer before the exit can play.

easeful ships all of it and gates each path on the attributes the host already writes. You never
pick one. It is worth knowing only when something looks wrong, which is why each component page
names the path it took.

## What it deliberately does not do

Motion, and nothing else. Focus trapping, positioning, dismissal, and ARIA stay with the host
component, which already does them properly. If a preset does not exist for what you need, add it
to the manifest and regenerate rather than writing one-off keyframes.

## Every component, with and without

Every page mounts the same component twice, once with the attribute and once without. Nothing
else differs between the two copies, so anything you see is the library.

- [Radix UI](/radix/dialog.md), 15 components, data-state, exits on keyframes
- [Base UI](/base-ui/dialog.md), 14 components, starting and ending styles, exits on transitions
- [No library](/native/dialog.md), 3 components, no framework, the browser writes the state
