Motion primitive
A motion primitive for animating the entry and exit of components.Early Access
The new Motion primitive and tokens are currently in early access. We may make frequent breaking changes with limited notice. We don't recommend using this without support from our team.
The Motion primitive is the recommended way to apply entry and exit animations in
@atlaskit/motion. It renders a wrapper around its children — no render-prop pattern required —
making it easy to compose with any React content, including ADS Primitives.
It supports two approaches for defining animations:
- Motion tokens — pass a pre-defined motion token to
enteringAnimationandexitingAnimationfor the simplest setup. - Custom xcss styles — pass
cssMapstyles toenteringAnimationXcssandexitingAnimationXcssfor full control overanimationName,animationDuration,animationTimingFunction, andanimationDelay.
Wrap the Motion primitive with <ExitingPersistence> to enable exit animations when elements are
removed from the DOM.
Using motion tokens
The simplest way to use the Motion primitive is with pre-defined motion tokens. Pass a motion token
to enteringAnimation and exitingAnimation to apply a paired entering and exiting animation.
import { useState } from 'react';
import Button from '@atlaskit/button/default/button';
import { cssMap, jsx } from '@atlaskit/css';
import ExitingPersistence from '@atlaskit/motion/exiting-persistence';
import Motion from '@atlaskit/motion/entering/motion';
import { token } from '@atlaskit/tokens';
import { Block } from '../utils/blocks';
import { Centered, RetryContainer } from '../utils/containers';
const styles = cssMap({
container: {
textAlign: 'center',
},
centered: {
height: '182px',
},
});
const MotionPrimitiveTokenExample = (): JSX.Element => {
const [isIn, setIsIn] = useState(true);
return (
<RetryContainer>
<div css={styles.container}>
<Button onClick={() => setIsIn((prev) => !prev)}>{isIn ? 'Exit' : 'Enter'}</Button>
<Centered css={styles.centered}>
<ExitingPersistence appear>
{isIn && (
<Motion
enteringAnimation={token('motion.blanket.enter')}
exitingAnimation={token('motion.blanket.exit')}
>
<Block />
</Motion>
)}
</ExitingPersistence>
</Centered>
</div>
</RetryContainer>
);
};
export default MotionPrimitiveTokenExample;Custom animations with keyframe tokens
For more control, use enteringAnimationXcss and exitingAnimationXcss with cssMap styles that
set animationName, animationDuration, and animationTimingFunction using motion tokens.
Multiple keyframes can be composed together by joining them in animationName (for example,
combining a scale and fade animation).
import { useState } from 'react';
import Button from '@atlaskit/button/default/button';
import { cssMap, jsx } from '@atlaskit/css';
import ExitingPersistence from '@atlaskit/motion/exiting-persistence';
import Motion from '@atlaskit/motion/entering/motion';
import { token } from '@atlaskit/tokens';
import { Block } from '../utils/blocks';
import { Centered, RetryContainer } from '../utils/containers';
const styles = cssMap({
container: {
textAlign: 'center',
},
centered: {
height: '182px',
},
entering: {
animationDuration: token('motion.duration.xlong'),
animationTimingFunction: token('motion.easing.out.practical'),
animationName: `${token('motion.keyframe.scale.in.medium')}, ${token('motion.keyframe.fade.in')}`,
},
exiting: {
animationDuration: token('motion.duration.long'),
animationTimingFunction: token('motion.easing.in.practical'),
animationName: `${token('motion.keyframe.scale.out.medium')}, ${token('motion.keyframe.fade.out')}`,
},
});
const MotionPrimitiveCustomExample = (): JSX.Element => {
const [isIn, setIsIn] = useState(true);
return (
<RetryContainer>
<div css={styles.container}>
<Button onClick={() => setIsIn((prev) => !prev)}>{isIn ? 'Exit' : 'Enter'}</Button>
<Centered css={styles.centered}>
<ExitingPersistence appear>
{isIn && (
<Motion enteringAnimationXcss={styles.entering} exitingAnimationXcss={styles.exiting}>
<Block />
</Motion>
)}
</ExitingPersistence>
</Centered>
</div>
</RetryContainer>
);
};
export default MotionPrimitiveCustomExample;Custom keyframes
For animations not covered by the built-in keyframe tokens, define your own CSS keyframes using
keyframes() from @compiled/react and reference them directly in the animationName property of
a cssMap style alongside token keyframes.
import { useState } from 'react';
import { keyframes } from '@compiled/react';
import Button from '@atlaskit/button/default/button';
import { cssMap, jsx } from '@atlaskit/css';
import ExitingPersistence from '@atlaskit/motion/exiting-persistence';
import Motion from '@atlaskit/motion/entering/motion';
import { token } from '@atlaskit/tokens';
import { Block } from '../utils/blocks';
import { Centered, RetryContainer } from '../utils/containers';
const slideIn = keyframes({
'0%': { transform: 'translateX(-24px)' },
'100%': { transform: 'translateX(0)' },
});
const slideOut = keyframes({
'0%': { transform: 'translateX(0)' },
'100%': { transform: 'translateX(-24px)' },
});
const styles = cssMap({
container: {
textAlign: 'center',
},
centered: {
height: '182px',
},
entering: {
animationDuration: token('motion.duration.xxlong'),
animationTimingFunction: token('motion.easing.out.practical'),
animationName: `${slideIn}, ${token('motion.keyframe.fade.in')}`,
},
exiting: {
animationDuration: token('motion.duration.xxlong'),
animationTimingFunction: token('motion.easing.in.practical'),
animationName: `${slideOut}, ${token('motion.keyframe.fade.out')}`,
},
});
const MotionPrimitiveCustomKeyframeExample = (): JSX.Element => {
const [isIn, setIsIn] = useState(true);
return (
<RetryContainer>
<div css={styles.container}>
<Button onClick={() => setIsIn((prev) => !prev)}>{isIn ? 'Exit' : 'Enter'}</Button>
<Centered css={styles.centered}>
<ExitingPersistence appear>
{isIn && (
<Motion enteringAnimationXcss={styles.entering} exitingAnimationXcss={styles.exiting}>
<Block />
</Motion>
)}
</ExitingPersistence>
</Centered>
</div>
</RetryContainer>
);
};
export default MotionPrimitiveCustomKeyframeExample;