A control that allows the user to toggle between checked and not checked.
By clicking this checkbox, you agree to the terms.
Installation
Install the packages
Add the shadcn/ui component
Animate it
The CLI finds your shadcn/ui components through components.json, swaps their primitive imports for Animate UI's and removes the CSS animation classes Motion replaces.
Usage
Import the primitive from Animate UI instead of Radix UI. Everything else stays the same.
import { Checkbox } from '@animate-ui/react/primitives/radix-ui';
export function Terms() {
return (
<label className="flex items-center gap-2">
<Checkbox.Root defaultChecked>
<Checkbox.Indicator>
<CheckIcon />
</Checkbox.Indicator>
</Checkbox.Root>
Accept terms and conditions
</label>
);
}Coming from the @radix-ui/react-checkbox package? Use the subpath: import * as CheckboxPrimitive from '@animate-ui/react/primitives/radix-ui/checkbox'.
Check animation
The check draws itself by default. With animation="pop" on the Indicator, it pops in from half its size instead, and shrinks back out:
<Checkbox.Root defaultChecked>
<Checkbox.Indicator animation="pop">
<CheckIcon />
</Checkbox.Indicator>
</Checkbox.Root>The same demo with checkAnimation="pop" (its Controls switch it back to draw):
By clicking this checkbox, you agree to the terms.
To pop every check of your app at once (checkboxes, menus, selects, comboboxes), set checkAnimation="pop" on an AnimateUIProvider instead.
Animation
The motion of Animate UI v1's checkbox:
- Indicator: the check draws itself when the box is checked. Its stroke (
pathLength) and its opacity go from 0 to 1 together in 0.3s, after a 0.2s delay, on Motion's default ease-out (v1's delay and curve, drawn a little slower than v1's 0.2s). Unchecking erases it in 0.3s, without the delay, then the indicator unmounts, as in Radix. The indicator element itself never moves: its content animates, so your layout and classes stay as they are. Content that isn't an SVG shape fades the same way. - With
animation="pop"(orcheckAnimation="pop"on anAnimateUIProvider), the check pops instead: after the same 0.2s, it springs in from half its size out of a 2px blur (stiffness 500, damping 24, a little past its size), and shrinks back into its blur in 0.15s when the box is unchecked. Its stroke stays whole. The demo's Controls switch between the two. - Root doesn't move: unlike v1's, it doesn't scale when pressed or hovered. The check (and, in the demo, the box's fill) answers the click.
- In the demo, the box's fill is a layer of its own (
before:in its classes) that grows from 70% while it fades in, in 300ms, and shrinks back as it fades out: the check draws over a box that is nearly full. The layer covers the box's border (before:-inset-px) and the border keeps its color, so no outline rings the growing fill. It's CSS, so copy the classes for the same effect; under reduced motion the layer only fades. - A box that is already checked when it appears (the page loading, a dialog opening) shows its check at rest, unlike v1's: only checking and unchecking animate it. On a server-rendered page, the check is there before any script runs.
- Checking with Space animates the same way: the state changes at once, the animation never delays it.
- Toggling again mid-animation turns back from where the check is.
indeterminateshows the indicator likechecked. An icon swapped for the indeterminate state draws without the delay, as v1's indeterminate line did.- Under reduced motion, the check only fades, drawn or popped (its stroke stays whole, nothing scales).
- With
forceMount, the unchecked indicator stays in the DOM with its check hidden. - The box's own colours are the component's CSS: shadcn/ui's
transition-colorsfades them.
API Reference
Every part takes exactly the props of Radix UI's Checkbox. Animate UI adds the animation props below. With asChild, a part renders your element with Radix's behaviour, without the animation.
Root
Radix UI props only. It isn't animated.
Indicator
Radix UI props, plus:
| Prop | Type | Default |
|---|---|---|
animation? | 'draw' | 'pop' | the closest AnimateUIProvider's checkAnimation, else 'draw' |
transition? | Transition | draw: { duration: 0.3, delay: 0.2, ease: [0, 0, 0.58, 1] }; pop: { type: 'spring', stiffness: 500, damping: 24, delay: 0.2 } |
exitTransition? | Transition | draw: { duration: 0.3, ease: [0, 0, 0.58, 1] }; pop: { duration: 0.15, ease: [0.23, 1, 0.32, 1] } |
AnimateUIProvider's tempo scales these transitions with everything else.