easeul

Questions

Why a Radix exit does not play, why nothing animates under Tailwind v4, and how to animate a height the browser refuses to interpolate.

Why does my Radix dialog have no exit animation?

Because Radix waits for animationend, not transitionend. Its Presence layer reads the computed animation-name on the closing node and unmounts immediately when there is none, so a transition-only implementation never gets to play. easeful ships a keyframe path for exactly this, gated on the data-state attribute Radix already writes.

I added data-motion and nothing animates at all. What is wrong?

Almost always the Tailwind v4 layer order. A layer's position is fixed where it is first named, so if @import "tailwindcss" comes first, easeful's deliberately low-priority layer lands after Tailwind's and loses. Declare @layer easeful, theme, base, components, utilities; before any import.

How do you animate height from zero to auto in CSS?

You animate a grid row instead. height: auto is not an interpolatable value, so auto to 0 is a discrete change that flips at the midpoint. A one-row grid going from grid-template-rows: 1fr to 0fr interpolates in every engine, because both values are concrete. That is what the collapse preset does.

Why does my accordion snap shut instead of animating closed?

A height-based collapse hangs at full height for half its duration and then snaps, because the browser cannot interpolate from auto. interpolate-size: allow-keywords fixes it and is Chromium-only. The collapse preset animates a grid row rather than a height, so it does not need that property.

Can I use easeful with Framer Motion or Motion?

Not on the same element. Both write transform, and easeful writes the translate and scale longhands, so the two take turns overwriting each other and the result reads as a stutter. Pick one owner per element. easeful is the right owner when the host component already publishes open and closed state.

Does any JavaScript ship to the browser?

None. easeful is CSS: a set of rules that match on state attributes the host component already writes. There is no provider, no hook, no wrapper component and no runtime. The only JavaScript involved is whatever your component library was already shipping to open and close things.

What if my component has no open and closed attribute?

Write data-motion-state="open" or "closed" yourself and the transition path matches on it exactly as it matches Base UI's ending styles. Base UI's scroll area needs this, because it reports only hovering and scrolling and never hides its own scrollbar. Any element you keep mounted and hide yourself needs it too.

Base UI warns that both transitions and animations were detected. Why?

Base UI inspects the panel's computed style and refuses to guess when it finds both. easeful gates its keyframe rules on Radix's data-state values, which Base UI never writes, so the library alone does not trigger it. If you see the warning, something else on that panel is setting an animation.

Can I use any preset on any component?

Nearly. Every component page carries a switcher listing every value the manifest allows on that host, so you can see each one on the component in front of you. The only host-specific preset is collapse, which the native <dialog> and [popover] hosts do not offer because a top-layer element has no panel to collapse.

Will it conflict with the CSS I have already written?

Not by default. Everything ships inside @layer easeful, and unlayered CSS beats layered CSS whatever its specificity, so your own rules win with no !important. On an element without data-motion the library declares nothing at all: the only global footprint is seven custom properties on :root and four keyframe names, all prefixed.

I set transition on the element myself and the exit stopped working. Why?

The transition shorthand resets transition-behavior to normal, and that property is what keeps an element with display: none around long enough to animate out. Your declaration wins, as it should, and it takes the exit with it. Use the transition-* longhands on that element, or leave the transition to the preset.

My own animation on the element broke more than the animation. Why?

Because Radix reads the computed animation-name to decide when to unmount a closing node. Overriding the library's animation there does not just change how the exit looks, it changes when the element disappears. Pick one owner for the animation on any element Radix keeps mounted for you.

My collapse panel stopped collapsing after I styled it. Why?

Check for display or overflow on the panel itself. The preset makes it a one-row grid with overflow: hidden, and an unlayered display: block or overflow: visible wins and quietly disables the mechanism while everything else keeps working. Style the child instead, which is where padding belongs anyway.

I already use a data-motion attribute for something else. What happens?

Those elements pick up the base transition setup, because the base rule matches the attribute rather than its value. Nothing animates unless your value is also a preset name, but the transition properties are set. Radix's navigation menu writes its own data-motion, and every rule in the library carries a guard against exactly that.

How do I change the duration or the easing?

Override a token in your own CSS. Everything easeful ships lives inside @layer easeful, so an unlayered declaration wins with no !important. Setting --motion-duration restyles every preset at once rather than one of them, which is the point of the token layer.

Do I need Tailwind to use it?

No. easeful is plain CSS with no build step and no dependencies, and it works in any app that can import a stylesheet. Tailwind is mentioned throughout the docs only because its v4 layer ordering is the one thing that can silently stop the library from working.