Motion
The easing curves, durations and reduced-motion check every component animates with, plus hooks for size morphs and sliding highlights.
easeOut
easeInOut
easeSpring
pnpm dlx shadcn@latest add https://hextaui.com/r/motion.jsonAdds the utility and anything it depends on to your project.
Copy and paste the following code into your project.
lib/motion.ts Update the import paths to match your project setup.
Every HextaUI component moves with the same few curves and durations, so the library feels like one thing.
- Ease out for things that respond to you. Elements entering, expanding or following a click start fast and settle, so the interface feels immediate.
- Short and interruptible. Most motion is 150 to 300ms. Anything that can be reversed starts from where it is now rather than restarting.
- Reduced motion is a second design, not an off switch. Movement turns into instant changes or plain fades, and state stays readable.
The theme defines the curves as Tailwind easing utilities, and lib/motion exports the same values for the Web Animations API.
| Class | Description |
|---|---|
ease-out-quint | easeOut in JS. The default for movement: popovers, highlights, size changes. |
ease-out-cubic | A softer ease out for color and shadow changes on hover and focus. |
ease-in-out-quart | easeInOut in JS. For movement between two resting states that nobody triggered directly. |
ease-spring | easeSpring in JS. A spring with a small overshoot, written as linear(), for things that land, like a toggle's thumb. |
ease-drawer | The iOS sheet curve for drawers and sheets that slide in from an edge. |
| duration. | Description |
|---|---|
press: 100 | Pressed state going down. |
release: 200 | Coming back up after a press. |
hover: 150 | Hover and focus feedback. |
enter: 200 | Elements appearing. |
exit: 150 | Elements leaving. Exits are faster than entrances, so they never hold anything up. |
morph: 300 | Size and position changes. |
prefersReducedMotion() reads the media query at call time. Check it when an animation starts rather than once on mount, so changing the system setting applies right away. It returns true on the server.
- Any DOM change inside the element triggers a morph, whether text, children or icons. Size changes from outside, like a resize, don't, so the element follows its container without lag.
- A change mid-morph continues from the current size. While it runs, the element has
data-morphing, which you can use to clip overflow or pause other transitions. - Keep the element at its natural size: no fixed width or height on the animated axis. Add
overflow-hiddenso the new content doesn't spill out while it grows. - It returns a callback ref. Combine it with other refs using
useMergedRef.
- The highlight is sized and translated with inline styles. Give it
absolute top-0and a transition ontransform,width,heightandopacity. - The hook watches the attribute you name with a
MutationObserver, so it follows state from anywhere, including Base UI's owndata-pressed,data-checkedoraria-current. data-visibleis set while something matches.data-instantis set when the highlight should jump: on first appearance, on resize and scroll, and under reduced motion. Style it asdata-instant:transition-opacity.- It measures with the bar's scale in mind, so it stays aligned inside a dialog that's still zooming in.
| Prop | Type | Default |
|---|---|---|
axisWhich dimension to animate. | "width" | "height" | – |
enabledWhether to animate. | boolean | true |
durationMilliseconds. | number | 300 |
easingAny CSS easing. | string | easeOut |
| Prop | Type | Default |
|---|---|---|
barRefThe positioned container. | RefObject<HTMLElement | null> | – |
highlightRefThe element to move. | RefObject<HTMLElement | null> | – |
selectorMatches the child to highlight. | string | – |
attributeThe attribute whose changes move the highlight. | string | "data-popup-open" |
| Export | Description |
|---|---|
easeOut | cubic-bezier(0.23, 1, 0.32, 1) |
easeInOut | cubic-bezier(0.77, 0, 0.175, 1) |
easeSpring | A linear() spring. |
duration | press, release, hover, enter, exit and morph. |
prefersReducedMotion() | Whether reduced motion is on. true on the server. |