Checkbox

A control that allows the user to toggle between checked and not checked.

<script setup lang="ts">
import type { CheckAnimation } from '@animate-ui/vue/provider'
import { AnimateUIProvider } from '@animate-ui/vue/provider'
import { ref, watch } from 'vue'
import { Checkbox } from '@/components/ui/checkbox'
import { Label } from '@/components/ui/label'

const props = withDefaults(
  defineProps<{
    checked?: boolean | 'indeterminate'
    checkAnimation?: CheckAnimation
  }>(),
  { checked: true, checkAnimation: 'draw' },
)

const checked = ref(props.checked)
watch(() => props.checked, (value) => (checked.value = value))
</script>

<template>
  <AnimateUIProvider :check-animation="checkAnimation">
    <div class="flex flex-col gap-6">
      <div class="flex items-center gap-3">
        <Checkbox id="terms" />
        <Label for="terms">Accept terms and conditions</Label>
      </div>
      <div class="flex items-start gap-3">
        <Checkbox id="terms-2" v-model="checked" />
        <div class="grid gap-2">
          <Label for="terms-2">Accept terms and conditions</Label>
          <p class="text-muted-foreground text-sm">
            By clicking this checkbox, you agree to the terms and conditions.
          </p>
        </div>
      </div>
      <div class="flex items-start gap-3">
        <Checkbox id="toggle" disabled />
        <Label for="toggle">Enable notifications</Label>
      </div>
      <Label class="hover:bg-accent/50 flex items-start gap-3 rounded-lg border p-3 transition-colors duration-300 has-[[aria-checked=true]]:border-blue-600 has-[[aria-checked=true]]:bg-blue-50 dark:has-[[aria-checked=true]]:border-blue-900 dark:has-[[aria-checked=true]]:bg-blue-950">
        <Checkbox
          id="toggle-2"
          :default-value="true"
          class="bg-background dark:bg-background before:bg-blue-600 before:[transition:scale_300ms_var(--tw-ease,ease),opacity_120ms_ease-out] data-[state=checked]:text-white dark:before:bg-blue-700"
        />
        <div class="grid gap-1.5 font-normal">
          <p class="text-sm leading-none font-medium">
            Enable notifications
          </p>
          <p class="text-muted-foreground text-sm">
            You can enable or disable notifications at any time.
          </p>
        </div>
      </Label>
    </div>
  </AnimateUIProvider>
</template>

Installation

Install the packages

npm install @animate-ui/vue motion-v
pnpm add @animate-ui/vue motion-v
yarn add @animate-ui/vue motion-v
bun add @animate-ui/vue motion-v

Add the shadcn-vue component

npx shadcn-vue@latest add checkbox
pnpm dlx shadcn-vue@latest add checkbox
yarn dlx shadcn-vue@latest add checkbox
bun x shadcn-vue@latest add checkbox

Animate it

The CLI finds your shadcn-vue components through components.json, swaps their primitive imports for Animate UI's and removes the CSS animation classes Motion replaces.

npx @animate-ui/cli@latest animate checkbox
pnpm dlx @animate-ui/cli@latest animate checkbox
yarn dlx @animate-ui/cli@latest animate checkbox
bun x @animate-ui/cli@latest animate checkbox

Usage

<script setup lang="ts">
import {
  CheckboxIndicator,
  CheckboxRoot,
} from '@animate-ui/vue/primitives/reka-ui';
</script>

<template>
  <CheckboxRoot id="terms">
    <CheckboxIndicator>
      <svg
        viewBox="0 0 24 24"
        fill="none"
        stroke="currentColor"
        stroke-width="2"
      >
        <path d="M5 12l5 5L20 7" />
      </svg>
    </CheckboxIndicator>
  </CheckboxRoot>
</template>

Check animation

The check draws itself by default. With animation="pop" on the CheckboxIndicator, it pops in from half its size instead, and shrinks back out:

<CheckboxRoot :default-value="true">
  <CheckboxIndicator animation="pop">✓</CheckboxIndicator>
</CheckboxRoot>

The same demo with check-animation="pop" (its Controls switch it back to draw):

To pop every check of your app at once (checkboxes, menus, selects, comboboxes), set check-animation="pop" on an AnimateUIProvider instead.

Animation

The motion of Animate UI's React 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 an ease-out. Unchecking erases it in 0.3s, without the delay, then the indicator unmounts, as in reka-ui. 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" (or check-animation="pop" on an AnimateUIProvider), 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: 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. It's CSS, so copy the classes for the same effect; under reduced motion the layer only fades. A dark fill color (a deep blue on a dark card) is nearly invisible while it is faint, so it would seem to appear full size: let its opacity arrive first, before:[transition:scale_300ms_var(--tw-ease,ease),opacity_120ms_ease-out], and the growth shows.
  • A box that is already checked when it appears (the page loading, a dialog opening) shows its check at rest: only checking and unchecking animate it.
  • 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.
  • indeterminate shows the indicator like checked. An icon swapped for the indeterminate state draws without the delay.
  • Under reduced motion, the check only fades, drawn or popped (its stroke stays whole, nothing scales).
  • With force-mount, the unchecked indicator stays in the DOM with its check hidden.
  • The box's own colours are the component's CSS: shadcn-vue's transition-colors fades them.

API Reference

Every part takes exactly the props of reka-ui's Checkbox. Animate UI adds the animation props below. With as-child, the indicator renders your element with reka-ui's behaviour, without the animation.

CheckboxRoot

reka-ui props only. It isn't animated.

CheckboxIndicator

reka-ui props, plus:

PropTypeDefault
animation?
'draw' | 'pop'the closest AnimateUIProvider's check-animation, else 'draw'
transition?
Transitiondraw: { duration: 0.3, delay: 0.2, ease: [0, 0, 0.58, 1] }; pop: { type: 'spring', stiffness: 500, damping: 24, delay: 0.2 }
exit-transition?
Transitiondraw: { 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.

Built by Skyleen. The source code is available on GitHub.

Last updated: 10/23/2026