Animation
Using CSS animations to animate Chakra UI components
We recommend using CSS animations to animate your Chakra UI components. This approach is performant, straightforward and provides a lot of flexibility.
You can animate both the mounting and unmounting phases of your components with better control.
Enter animation
When a disclosure component (popover, dialog) is open, the data-state
attribute is set to open. This maps to data-state=open and can be styled
with _open pseudo prop.
<Box
data-state="open"
_open={{
animation: "fade-in 300ms ease-out",
}}
>
This is open
</Box>Here's an example that uses keyframes to create a fade-in animation:
@keyframes fade-in {
from {
opacity: 0;
}
to {
opacity: 1;
}
}Exit animation
When a disclosure component (popover, dialog) is closed, the data-state
attribute is set to closed. This maps to data-state=closed and can be styled
with _closed pseudo prop.
<Box
data-state="closed"
_closed={{
animation: "fadeOut 300ms ease-in",
}}
>
This is closed
</Box>Here's an example that uses keyframes to create a fade-out animation:
@keyframes fadeOut {
from {
opacity: 1;
}
to {
opacity: 0;
}
}Composing animations
Use the animationName prop to compose multiple animations together. This makes
it easy to create complex animations with multiple keyframes.
<Box
data-state="open"
_open={{
animationName: "fade-in, scale-in",
animationDuration: "300ms",
}}
_closed={{
animationName: "fade-out, scale-out",
animationDuration: "120ms",
}}
>
This is a composed animation
</Box>Reduced motion
The built-in component animations play regardless of the user's
prefers-reduced-motion setting. To disable the enter/exit animations for users
who prefer reduced motion, add this rule to your globalCss config:
import { createSystem, defaultConfig, defineConfig } from "@chakra-ui/react"
const config = defineConfig({
globalCss: {
'[data-state="open"], [data-state="closed"]': {
_motionReduce: {
animationDuration: "1ms !important",
},
},
},
})
export const system = createSystem(defaultConfig, config)This collapses the enter/exit animations of every disclosure component (dialog,
drawer, menu, popover, tooltip, etc.) to 1ms when reduced motion is enabled.
A few details worth knowing:
- Scope to
data-state="open"anddata-state="closed"rather than targeting all elements. A blanket rule also collapses looping animations (Spinner,Skeleton, indeterminateProgress), turning them into a flicker. Loading indicators are considered essential motion and are best left running. animation: nonealso works. The presence logic detects it and unmounts exiting components immediately. A1msduration is suggested because it keepsanimationendevents firing for any custom code that listens for them.- The
!importantis required because the rule lives in a lower CSS layer than component recipes.
When building custom animations, use the _motionReduce condition to provide a
reduced-motion alternative:
<Box
data-state="open"
_open={{ animationName: "slide-from-bottom, fade-in" }}
_motionReduce={{ animationName: "fade-in" }}
>
Slides on screen, but only fades for reduced motion users
</Box>