Animation
Yamada UI provides many utilities, such as Motion
, a component specialized for animation, and useAnimation
, which can be described like CSS's keyframes
.
CSS Animation
Yamada UI provides useAnimation
and useDynamicAnimation
that can be described like CSS's keyframes
. Also, by setting tokens in the theme, you can ensure consistency of animation in the project.
Usage of useAnimation
useAnimation
sets arguments such as keyframes
, and passes the generated animation
to props
.
keyframes
: Set the style of keyframes (or intermediate points) along the flow of the animation. The values of each style can use the tokens of the Yamada UI style system and theme.duration
: Set the length of time required for one animation cycle.timingFunction
: Set how the animation progresses. This defines the acceleration curve, setting how the animation between keyframes progresses.delay
: Set the delay time from when the element is loaded until the animation begins.iterationCount
: Set the number of times the animation repeats. To repeat the animation indefinitely, specifyinfinite
.direction
: Set whether to animate in the reverse direction and repeat when the animation sequence is completed, or to reset to the initial state and repeat the animation.fillMode
: Set whether to apply the specified style before and after the execution of the animation.playState
: Set whether to pause or resume the animation.
The animation settings are based on @keyframes and Using CSS animations.
Editable example
const animation = useAnimation({ keyframes: { "0%": { bg: "red.500", }, "20%": { bg: "green.500", }, "40%": { bg: "purple.500", }, "60%": { bg: "yellow.500", }, "80%": { bg: "blue.500", }, "100%": { bg: "red.500", }, }, duration: "10s", iterationCount: "infinite", timingFunction: "linear", }) return <Box w="full" h="xs" animation={animation} />
Using Multiple Animations
By passing an array of animations to useAnimation
, you can use multiple animations.
Editable example
const animation = useAnimation([ { keyframes: { "0%": { bg: "red.500", }, "20%": { bg: "green.500", }, "40%": { bg: "purple.500", }, "60%": { bg: "yellow.500", }, "80%": { bg: "blue.500", }, "100%": { bg: "red.500", }, }, duration: "10s", iterationCount: "infinite", timingFunction: "linear", }, { keyframes: { "0%": { h: "xs", }, "50%": { h: "md", }, "100%": { h: "xs", }, }, duration: "10s", iterationCount: "infinite", timingFunction: "linear", }, { keyframes: { "0%": { w: "full", }, "50%": { w: "50%", }, "100%": { w: "full", }, }, duration: "10s", iterationCount: "infinite", timingFunction: "linear", }, ]) return ( <Box h="md"> <Box w="full" h="xs" animation={animation} /> </Box> )
Responsive Styles
By passing an object to animation
in props
, it supports PC-first responsive styles.
Editable example
const desktopAnimation = useAnimation({ keyframes: { "0%": { bg: "red.500", }, "20%": { bg: "green.500", }, "40%": { bg: "purple.500", }, "60%": { bg: "yellow.500", }, "80%": { bg: "blue.500", }, "100%": { bg: "red.500", }, }, duration: "10s", iterationCount: "infinite", timingFunction: "linear", }) const tabletAnimation = useAnimation({ keyframes: { "0%": { bg: "cyan.500", }, "20%": { bg: "emerald.500", }, "40%": { bg: "pink.500", }, "60%": { bg: "amber.500", }, "80%": { bg: "sky.500", }, "100%": { bg: "cyan.500", }, }, duration: "10s", iterationCount: "infinite", timingFunction: "linear", }) return ( <Box w="full" h="xs" animation={{ base: desktopAnimation, md: tabletAnimation }} /> )
If you want to learn more about responsive styles, please check here.
Color Modes
By passing an array to animation
in props
, it supports color modes.
Editable example
const lightAnimation = useAnimation({ keyframes: { "0%": { bg: "red.500", }, "20%": { bg: "green.500", }, "40%": { bg: "purple.500", }, "60%": { bg: "yellow.500", }, "80%": { bg: "blue.500", }, "100%": { bg: "red.500", }, }, duration: "10s", iterationCount: "infinite", timingFunction: "linear", }) const darkAnimation = useAnimation({ keyframes: { "0%": { bg: "red.800", }, "20%": { bg: "green.800", }, "40%": { bg: "purple.800", }, "60%": { bg: "yellow.800", }, "80%": { bg: "blue.800", }, "100%": { bg: "red.800", }, }, duration: "10s", iterationCount: "infinite", timingFunction: "linear", }) return <Box w="full" h="xs" animation={[lightAnimation, darkAnimation]} />
If you want to learn more about color modes, please check here.
Usage of useDynamicAnimation
useDynamicAnimation
takes an object as an argument. The keys of the object become the keys of the animation, and the animation changes by passing the key as an argument to setState
.
Editable example
const [animation, setAnimation] = useDynamicAnimation({ moveLeft: { keyframes: { "0%": { transform: "translateX(400%)", }, "100%": { transform: "translateX(0%)", }, }, duration: "slower", fillMode: "forwards", timingFunction: "ease-in-out", }, moveRight: { keyframes: { "0%": { transform: "translateX(0%)", }, "100%": { transform: "translateX(400%)", }, }, duration: "slower", fillMode: "forwards", timingFunction: "ease-in-out", }, }) return ( <VStack alignItems="flex-start"> <Button onClick={() => setAnimation((prev) => prev === "moveRight" ? "moveLeft" : "moveRight", ) } > Click me! </Button> <Box bg="primary" p="md" rounded="md" color="white" animation={animation}> Box </Box> </VStack> )
Combining with multiple animations
It is also possible to combine dynamic animations with multiple animations.
Editable example
const [animation, setAnimation] = useDynamicAnimation< Record<"moveLeft" | "moveRight", AnimationStyle[]> >({ moveLeft: [ { keyframes: { "0%": { transform: "translateX(400%)", }, "100%": { transform: "translateX(0%)", }, }, duration: "slower", fillMode: "forwards", timingFunction: "ease-in-out", }, { keyframes: { "0%": { bg: "secondary", }, "100%": { bg: "primary", }, }, duration: "slower", fillMode: "forwards", timingFunction: "ease-in-out", }, ], moveRight: [ { keyframes: { "0%": { transform: "translateX(0%)", }, "100%": { transform: "translateX(400%)", }, }, duration: "slower", fillMode: "forwards", timingFunction: "ease-in-out", }, { keyframes: { "0%": { bg: "primary", }, "100%": { bg: "secondary", }, }, duration: "slower", fillMode: "forwards", timingFunction: "ease-in-out", }, ], }) return ( <VStack alignItems="flex-start"> <Button onClick={() => setAnimation((prev) => prev === "moveRight" ? "moveLeft" : "moveRight", ) } > Click me! </Button> <Box bg="primary" p="md" rounded="md" color="white" animation={animation}> Box </Box> </VStack> )
Using Theme Tokens
If you are going to use the theme's animation tokens, please first check Theme Operation.
Create animation tokens and set them in the theme.
./theme/tokens/animations.ts
import { ThemeAnimationTokens } from "@yamada-ui/react"export const animations: ThemeAnimationTokens = {gradient: {keyframes: {"0%": {bg: "red.500",},"20%": {bg: "green.500",},"40%": {bg: "purple.500",},"60%": {bg: "yellow.500",},"80%": {bg: "blue.500",},"100%": {bg: "red.500",},},duration: "10s",iterationCount: "infinite",timingFunction: "linear",},}
./theme/tokens/index.ts
import { animations } from "./animations"export const tokens = { animations }
./theme/index.ts
import { extendTheme, UsageTheme } from "@yamada-ui/react"import { tokens } from "./tokens"const customTheme: UsageTheme = {...tokens,}export const theme = extendTheme(customTheme)()
It is recommended to use type completion for the animation tokens you created. For type completion, use the CLI.
The created tokens are specified in the animation
of the component's props
. They can also be used with useAnimation
and useDynamicAnimation
.
Editable example
return <Box w="full" h="xs" animation="gradient" />
Motion
You can easily implement many animations by using the Motion
component.
The Motion
component internally uses Framer Motion. If you want to know more about the component's features, please check here.
Animation
By setting styles to initial
, animate
, and exit
, you can implement animations to elements. You can also set the required time and delay time in transition
.
initial
: This is a style object for the initial value of the element.animate
: This is a style object that you want to change (animate) frominitial
.exit
: This is a style object that you want to change (animate) when the element is removed from theDOM tree
.transition
: This is an object to set the required time and delay time.
The style objects used in initial
, animate
, exit
, and transition
are not Style props
of Yamada UI. For the properties of the style object, please check Framer Motion.
Editable example
<Center p="lg"> <Motion initial={{ x: -100 }} animate={{ x: 100 }} transition={{ duration: 2, ease: "easeInOut", repeat: Infinity, }} p="md" rounded="md" bg="primary" color="white" > Look me! </Motion> </Center>
Usage of AnimatePresence
In React, animations are not maintained when elements are removed from the DOM tree
. By wrapping with the AnimatePresence
component, the element is maintained on the DOM tree
until the animation ends.
Editable example
const [isVisible, { toggle }] = useBoolean() return ( <> <Button onClick={toggle}>Click me!</Button> <Center h="3xs" gap="md"> <AnimatePresence> {isVisible ? ( <Motion initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }} transition={{ duration: 1 }} bg="primary" color="white" p="md" rounded="md" > Enabled "AnimatePresence" </Motion> ) : null} </AnimatePresence> {isVisible ? ( <Motion initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }} transition={{ duration: 1 }} bg="secondary" color="white" p="md" rounded="md" > Disabled "AnimatePresence" </Motion> ) : null} </Center> </> )
Usage of useAnimationFrame
useAnimationFrame
executes a callback once for each animation frame of the element. The callback can get the total time since the callback was first called and the total time from the last animation frame.
Editable example
const containerRef = useRef<HTMLDivElement>(null) useAnimationFrame((time, delta) => { const rotate = Math.sin(time / 10000) * 200 const y = (1 + Math.sin(time / 1000)) * -50 containerRef.current.style.transform = `translateY(${y}px) rotateX(${rotate}deg) rotateY(${rotate}deg)` }) const sides = useMemo( () => [ { transform: "rotateY(0deg) translateZ(60px)", bg: "red.500" }, { transform: "rotateY(90deg) translateZ(60px)", bg: "orange.500" }, { transform: "rotateY(180deg) translateZ(60px)", bg: "pink.500" }, { transform: "rotateY(-90deg) translateZ(60px)", bg: "purple.500" }, { transform: "rotateX(90deg) translateZ(60px);", bg: "blue.500" }, { transform: "rotateX(-90deg) translateZ(60px)", bg: "green.500" }, ], [], ) return ( <Center h="md"> <Box css={{ perspective: "800px" }} w="120px" h="120px"> <Box ref={containerRef} position="relative" w="120px" h="120px" transformStyle="preserve-3d" > {sides.map(({ transform, bg }) => ( <Box key={bg.split(".")[0]} position="absolute" w="full" h="full" bg={bg} opacity="0.6" transform={transform} /> ))} </Box> </Box> </Center> )
Keyframes
By passing an array to the property of the style object, you can set keyframes. Each keyframe is placed at an equal interval throughout the animation. You can override the interval by setting times
in transition
.
Editable example
<Center h="md"> <Motion animate={{ scale: [1, 2, 2, 1, 1], rotate: [0, 0, 180, 180, 0], borderRadius: ["0%", "0%", "50%", "50%", "0%"], }} transition={{ duration: 2, ease: "easeInOut", times: [0, 0.2, 0.5, 0.8, 1], repeat: Infinity, repeatDelay: 1, }} w="3xs" h="3xs" bg="primary" /> </Center>
If you want to know more about animation, please check here.
Gestures
You can detect hover, click, tap, and focus and implement animations.
Hover
The hover gesture detects whether the pointer has moved over or away from an element.
The difference between onMouseEnter
and onMouseLeave
is that they are guaranteed to fire only as a result of actual mouse events.
whileHover
: An animation that fires when the element is hovered.onHoverStart
: A callback function that fires when the pointer moves over the element.onHoverEnd
: A callback function that fires when the pointer moves away from the element.
Editable example
<Center p="lg"> <Motion as="button" whileHover={{ scale: 1.1 }} onHoverStart={(ev) => console.log("Hover starts")} onHoverEnd={(ev) => console.log("Hover ends")} p="md" rounded="md" bg="primary" color="white" > Hover me! </Motion> </Center>
If you want to know more about hover animations, please check here.
Click/Tap
The click/tap gesture detects whether the primary pointer (such as a left click or touch) has pressed down or released the same element.
whileTap
: An animation that fires when the element is clicked/tapped.onTap
: A callback function that fires when a click/tap on the element has successfully ended.onTapStart
: A callback function that fires when a click/tap on the element has started.onTapCancel
: A callback function that fires when a click/tap has been cancelled (released outside the element).
Editable example
<Center p="lg"> <Motion as="button" whileTap={{ scale: 1.1 }} onTap={(ev, { point }) => console.log("Tap ends", "x:", point.x, "y:", point.y) } onTapStart={(ev, { point }) => console.log("Tap starts", "x:", point.x, "y:", point.y) } onTapCancel={(ev, { point }) => console.log("Tap cancels", "x:", point.x, "y:", point.y) } p="md" rounded="md" bg="primary" color="white" > Click me! </Motion> </Center>
If you want to know more about click/tap animations, please check here.
Focus
The focus gesture detects whether an element has been focused, following the same rules as the CSS
selector focus-visible
.
Editable example
<Center p="lg"> <Motion as="button" whileFocus={{ scale: 1.1 }} p="md" rounded="md" bg="primary" color="white" > Focus me! </Motion> </Center>
If you want to know more about focus animations, please check here.
Drag
Detects the movement of the pointer and makes the element follow it.
If you want to enable dragging on an element, set drag
to true
or pass x
or y
. If you pass x
or y
, it will only follow the x-axis
or y-axis
.
onDrag
: A callback function that fires during dragging.onDragStart
: A callback function that fires when dragging starts.onDragEnd
: A callback function that fires when dragging ends.
Editable example
<Center h="md" gap="md"> <Motion drag onDrag={(ev, { point }) => console.log("Drag", "x:", point.x, "y:", point.y) } onDragStart={(ev, { point }) => console.log("Drag starts", "x:", point.x, "y:", point.y) } onDragEnd={(ev, { point }) => console.log("Drag ends", "x:", point.x, "y:", point.y) } p="md" cursor="grab" _active={{ cursor: "grabbing" }} rounded="md" bg="primary" color="white" > Drag me! </Motion> <Motion drag="x" p="md" cursor="grab" _active={{ cursor: "grabbing" }} rounded="md" bg="secondary" color="white" > Only X </Motion> <Motion drag="y" p="md" cursor="grab" _active={{ cursor: "grabbing" }} rounded="md" bg="warning" color="white" > Only Y </Motion> </Center>
Constraining the Possible Area
You can constrain the possible area of dragging by passing an object with values (in pixels) for top
, right
, bottom
, and left
to dragConstraints
, or by passing a ref
.
Editable example
const containerRef = useRef<HTMLDivElement>(null) return ( <Center ref={containerRef} h="md" gap="md"> <Motion drag dragConstraints={containerRef} p="md" cursor="grab" _active={{ cursor: "grabbing" }} rounded="md" bg="primary" color="white" > Drag me! </Motion> <Motion drag dragConstraints={{ top: 0, right: 100, bottom: 100, left: 0, }} p="md" cursor="grab" _active={{ cursor: "grabbing" }} rounded="md" bg="secondary" color="white" > Only right & bottom </Motion> </Center> )
Elasticity and Momentum
By passing true
, false
, a number
, or an object with numbers set for top
, right
, bottom
, and left
to dragElastic
, you can set the degree of allowed movement outside the constraints. 0
means no movement, and 1
means full movement. The default is set to 0.5
.
Editable example
const containerRef = useRef<HTMLDivElement>(null) return ( <Center ref={containerRef} h="md" p="md" gap="md"> <Motion drag dragConstraints={containerRef} dragElastic={0} p="md" cursor="grab" _active={{ cursor: "grabbing" }} rounded="md" bg="primary" color="white" > Drag me! </Motion> </Center> )
By passing true
or false
to dragMomentum
, you can apply the momentum of the pan gesture to the element. The default is set to true
.
Editable example
const containerRef = useRef<HTMLDivElement>(null) return ( <Center ref={containerRef} h="md" p="md" gap="md"> <Motion drag dragConstraints={containerRef} dragMomentum={false} p="md" cursor="grab" _active={{ cursor: "grabbing" }} rounded="md" bg="primary" color="white" > Drag me! </Motion> </Center> )
If you want to know more about drag animation, please check here.
Scroll
You can detect when an element enters and leaves the viewport and implement animations.
whileInView
: An animation that fires when an element enters the viewport.viewport
: An object that sets how the viewport is detected.once
: Iftrue
, the animation fires only once when the element enters the viewport.root
: By passing aref
, the viewport of the specified element is used. If no element is specified, the window's viewport is used.margin
: The margin added to the viewport when detecting whether an element has entered the viewport. By default, it is"0px"
. It is described like themargin
ofCSS
(e.g.,"0px -20px 0px 100px"
).amount
: By passing"some"
,"all"
, or a number, you can set the height of the element that needs to intersect with the viewport. The default is"some"
.
onViewportEnter
: A callback function that fires when an element enters the viewport.onViewportLeave
: A callback function that fires when an element leaves the viewport.
Editable example
const containerRef = useRef<HTMLDivElement>(null) return ( <> <Text>Please scroll</Text> <Box ref={containerRef} h="md" p="md" overflowY="auto"> <HStack mt="24rem" justifyContent="center"> <Motion initial={{ x: -100 }} whileInView={{ x: 0 }} viewport={{ root: containerRef }} transition={{ duration: 1 }} onViewportEnter={(entry) => console.log("Scroll entires", entry)} onViewportLeave={(entry) => console.log("Scroll leaves", entry)} p="md" rounded="md" bg="primary" color="white" > You found me! </Motion> <Motion initial={{ x: 100 }} whileInView={{ x: 0 }} viewport={{ once: true, root: containerRef }} transition={{ duration: 1 }} onViewportEnter={(entry) => console.log("Scroll entires", entry)} onViewportLeave={(entry) => console.log("Scroll leaves", entry)} p="md" rounded="md" bg="secondary" color="white" > Once me! </Motion> </HStack> </Box> </> )
Usage of useScroll
useScroll
returns four instances. By using these instances, you can implement animations according to the scroll.
scrollX
: An instance of scroll information (x-axis
) between the window's offsets.scrollY
: An instance of scroll information (y-axis
) between the window's offsets.scrollXProgress
: An instance of scroll information (x-axis
) between the offsets of the element specified in the argument.scrollYProgress
: An instance of scroll information (y-axis
) between the offsets of the element specified in the argument.
Editable example
const containerRef = useRef<HTMLDivElement>(null) const { scrollYProgress } = useScroll({ container: containerRef }) const scale = useTransform(scrollYProgress, [0, 1], [0.2, 2]) return ( <Box ref={containerRef} position="relative" h="md" overflowY="auto"> <Box h="9xl"> <Box w="2xs" h="2xs" position="sticky" top="50%" left="50%" transform="translate(-50%, -50%)" > <Motion style={{ scale }} w="full" h="full" overflow="hidden" bg={["blackAlpha.200", "whiteAlpha.200"]} rounded="3xl" > <Motion style={{ scaleY: scrollYProgress }} w="inherit" h="inherit" bg="primary" transformOrigin="50% 100%" /> </Motion> </Box> </Box> </Box> )
If you want to know more about scroll animations, please check here.
Global Configuration
If you want to apply a common configuration to the Motion
component across your entire project, set it in the config
of UIProvider
.
import { UIProvider, extendConfig } from "@yamada-ui/react"const customConfig = extendConfig({motion: {config: {transition: { duration: 2 },},},})const App = () => {return (<UIProvider config={customConfig}><YourApplication /></UIProvider>)}
If you want to change the configuration, please check here.
Edit this page on GitHub