# Checkbox

A shadcn-based Checkbox with fluid indicator transitions, mixed state, and accessible form semantics.

> For the complete documentation index, see [llms.txt](/llms.txt). Markdown variants are available by appending `.md` to any URL or sending an `Accept: text/markdown` header. An agent skill is available at [/.well-known/agent-skills/site-skill.md](/.well-known/agent-skills/site-skill.md).





## Playground [#playground]

```tsx
"use client";

import { Checkbox } from "@/components/ui/checkbox";

export function CheckboxDemo() {
  return (
    <label className="flex items-center gap-3 text-sm">
      <Checkbox animated={true} aria-invalid={false} aria-label={"Enable notifications"} defaultChecked={false} disabled={false} indeterminate={false} readOnly={false} />
      <span>{"Enable notifications"}</span>
    </label>
  );
}
```

## Installation [#installation]

Initialize shadcn in your React project, then install this component from the Vandor UI registry:

```bash
npx shadcn@latest add https://vandor-ui.vercel.app/r/checkbox.json
```

## Dependencies [#dependencies]

Installed automatically by the registry CLI. For manual setup:

```bash
npm install @base-ui/react@^1.8.0 cn motion@^12.38.0
```

## Usage [#usage]

```tsx
"use client";

import { Checkbox } from "@/components/ui/checkbox";

export function Demo() {
  return (
    <label className="flex items-center gap-3 text-sm">
      <Checkbox defaultChecked />
      Enable notifications
    </label>
  );
}
```

Use your configured UI alias if it differs. Requires React 19, Tailwind CSS 4 with shadcn theme tokens (`input`, `primary`, `primary-foreground`, `ring`, and `destructive`), Base UI 1.8+, and Motion 12.38+. No website-specific CSS, providers, or icon package is needed. The upstream MIT notice is included in the source. For manual installation, install the packages listed above and copy the complete Source into your configured UI directory, preserving the notice.

## Examples [#examples]

### Basic [#basic]

Wrap the control and text in a label to make the whole row clickable. `defaultChecked` sets the initial uncontrolled state; changing it does not update an existing checkbox. The playground remounts the control when this configuration changes.

<ComponentPreview name="checkbox-demo" title="checkbox-demo.tsx">
  <CheckboxDemo />
</ComponentPreview>

### States and validation [#states-and-validation]

Disabled controls cannot change and are omitted from submission. Read-only controls retain focusability and their submitted value. Connect errors or helper text through `aria-describedby`; `aria-invalid` exposes invalid semantics and styling but does not implement validation itself.

<ComponentPreview name="checkbox-states" title="checkbox-states.tsx">
  <CheckboxStates />
</ComponentPreview>

### Controlled selection and indeterminate state [#controlled-selection-and-indeterminate-state]

Use `checked` with `onCheckedChange` for controlled state. The callback receives a boolean and Base UI event details immediately, before any animation finishes. `details.cancel()` rejects an update. `indeterminate` is independent of `checked`: a mixed state displays a line and announces `aria-checked="mixed"`. Manage both values yourself, as in this select-all example.

<ComponentPreview name="checkbox-group" title="checkbox-group.tsx">
  <CheckboxGroup />
</ComponentPreview>

### Native form submission and reset [#native-form-submission-and-reset]

Set `name` and `value` to include a checked checkbox in `FormData`. The default checked value is `"on"`; unchecked inputs are omitted unless `uncheckedValue` is provided. Base UI does not synchronize uncontrolled visual state with native form reset. When reset is needed, use controlled state and restore it in the form's `onReset`, as shown here. Use `required` for native constraint validation; use `form` to associate an external control with a form.

<ComponentPreview name="checkbox-form" title="checkbox-form.tsx">
  <CheckboxForm />
</ComponentPreview>

## Behavior & Accessibility [#behavior--accessibility]

* Base UI preserves checkbox keyboard behavior: Tab focuses and Space toggles. Enter is not the toggle key.
* The default visible root is a span with checkbox semantics plus a hidden native input. Use an enclosing label or Base UI Field for naming. For a sibling `htmlFor` label, use `render={<button />}` and `nativeButton` as described in the [Base UI documentation](https://base-ui.com/react/components/checkbox).
* `ref` targets the visible root; `inputRef` targets the native input. Base UI root props, event handlers, state-aware `className`/`style`, and composition are forwarded. Children are reserved for the built-in indicator.
* The check mark draws along its stroke, from the short arm into the long arm, over 220 ms at a steady pace. Unchecking draws it back over 140 ms, hiding it only after withdrawal finishes. Mixed-state morphing uses a short 180 ms ease-out transition. There is no scale, bounce, fade-in, or delayed state update. Reduced-motion preferences make indicator transitions immediate and disable CSS color transitions. `animated={false}` disables indicator motion explicitly; color transitions remain unless reduced motion is preferred.
* Light and dark styles inherit your shadcn tokens. A custom `className` merges with defaults. Do not remove accessible focus treatment without a replacement.

## Storybook Stories (Optional) [#storybook-stories-optional]

Install Checkbox first, then add portable stories to your existing React Storybook:

```bash
npx shadcn@latest add https://vandor-ui.vercel.app/r/checkbox-stories.json
```

This installs only `checkbox.stories.tsx` beside Checkbox in your configured UI directory, without reinstalling Checkbox, adding packages, or editing `.storybook/`. Review overwrite prompts for existing stories. Supply `@storybook/react` matching your installed Storybook version and React; stories are typechecked and composed against Storybook React 10.6.1. No extra example dependencies are required.

Include `../components/ui/**/*.stories.@(js|jsx|mjs|ts|tsx)` in your existing `.storybook/main.ts` stories patterns, adjusting for your UI directory. Import your application's Tailwind/global stylesheet in `.storybook/preview.ts`; include the UI/stories directory in Tailwind source scanning. Stories inherit your theme and decorators. Apply your dark-theme selector inside the preview iframe, not only to the Storybook manager. The `autodocs` tag needs your existing `@storybook/addon-docs` configuration; Canvas and Controls do not.

## Props [#props]

| Prop            | Type                                                                  | Default                | Description                                                                                                   |
| --------------- | --------------------------------------------------------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------- |
| animated        | boolean                                                               | true                   | Draws and withdraws the check stroke, with mixed-state morphing. Reduced motion makes these immediate.        |
| aria-invalid    | boolean \| 'true' \| 'false'                                          | Not set                | Invalid styling and semantics. Associate error text using aria-describedby.                                   |
| checked         | boolean                                                               | Not set                | Controlled checked state. Pair with onCheckedChange.                                                          |
| className       | string \| ((state: Checkbox.Root.State) => string)                    | Not set                | Classes merged with defaults, optionally derived from Base UI checkbox state.                                 |
| defaultChecked  | boolean                                                               | false                  | Initial uncontrolled checked state. Use checked for controlled state.                                         |
| disabled        | boolean                                                               | false                  | Prevents interaction and excludes the input from form submission.                                             |
| indeterminate   | boolean                                                               | false                  | Displays a mixed state independently of checked. Manage this prop when selecting a group.                     |
| inputRef        | Ref\<HTMLInputElement>                                                | Not set                | Ref to the native input. The ordinary ref targets the visible root.                                           |
| name            | string                                                                | Not set                | Name of the hidden native form input.                                                                         |
| nativeButton    | boolean                                                               | false                  | Set true when render supplies a native button.                                                                |
| onCheckedChange | (checked: boolean, details: Checkbox.Root.ChangeEventDetails) => void | Not set                | Called immediately with the next boolean and Base UI event details. Call details.cancel() to reject a change. |
| readOnly        | boolean                                                               | false                  | Prevents changes while preserving focus and submitted value.                                                  |
| render          | ReactElement \| ComponentRenderFn                                     | Not set (span)         | Base UI root composition. For sibling labels, use render=\{\<button />} and nativeButton.                     |
| required        | boolean                                                               | false                  | Requires a checked input for native form validation.                                                          |
| uncheckedValue  | string                                                                | Not set                | Optional submitted value when unchecked. Otherwise unchecked inputs are omitted.                              |
| value           | string                                                                | "on" (form submission) | Submitted value when checked.                                                                                 |

The reference lists component-specific props and selected native props, not the complete React API. Other native attributes are forwarded to the rendered element.

## Source [#source]

```tsx
"use client";

// Adapted from shadcn/ui's Base Nova Checkbox (MIT).
// https://ui.shadcn.com/docs/components/base/checkbox
/*
MIT License
Copyright (c) 2023 shadcn

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
import { Checkbox as CheckboxPrimitive } from "@base-ui/react/checkbox";
import { cn } from "cn";
import { motion, useReducedMotion } from "motion/react";
import type { ComponentProps } from "react";

export type CheckboxProps = Omit<
  ComponentProps<typeof CheckboxPrimitive.Root>,
  "children"
> & {
  /** Animate indicator changes. Reduced-motion preferences take precedence. */
  animated?: boolean;
};

export const Checkbox = ({
  className,
  animated = true,
  ...props
}: CheckboxProps) => {
  const reducedMotion = useReducedMotion();
  const shouldAnimate = animated && !reducedMotion;

  return (
    <CheckboxPrimitive.Root
      {...props}
      data-slot="checkbox"
      className={(state) =>
        cn(
          "peer relative flex size-4 shrink-0 items-center justify-center rounded-[4px] border border-input outline-none transition-colors duration-150 motion-reduce:transition-none after:absolute after:-inset-x-3 after:-inset-y-2 focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 data-disabled:cursor-not-allowed data-disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 data-checked:border-primary data-checked:bg-primary data-checked:text-primary-foreground data-indeterminate:border-primary data-indeterminate:bg-primary data-indeterminate:text-primary-foreground dark:bg-input/30 dark:data-checked:bg-primary dark:data-indeterminate:bg-primary",
          typeof className === "function" ? className(state) : className
        )
      }
    >
      <CheckboxPrimitive.Indicator
        keepMounted
        data-slot="checkbox-indicator"
        render={(indicatorProps, state) => {
          const visible = state.checked || state.indeterminate;
          const strokeDuration = visible ? 0.22 : 0.14;
          return (
            <span
              {...indicatorProps}
              aria-hidden="true"
              className="grid place-content-center text-current"
            >
              <span className="grid place-content-center">
                <svg
                  aria-hidden="true"
                  className="size-3.5"
                  viewBox="0 0 24 24"
                  fill="none"
                  stroke="currentColor"
                  strokeWidth="3"
                  strokeLinecap="round"
                  strokeLinejoin="round"
                >
                  <motion.path
                    initial={false}
                    animate={{
                      d: state.indeterminate
                        ? "M5 12 L12 12 L19 12"
                        : "M5 12 L10 17 L19 7",
                      opacity: visible ? 1 : 0,
                      pathLength: visible ? 1 : 0,
                    }}
                    transition={{
                      d: {
                        duration: shouldAnimate ? 0.18 : 0,
                        ease: [0.22, 1, 0.36, 1],
                      },
                      // Keep the stroke visible during withdrawal. Hide the
                      // round-cap dot only after the path has drawn back to zero.
                      opacity: {
                        delay: shouldAnimate && !visible ? 0.14 : 0,
                        duration: 0,
                      },
                      pathLength: {
                        duration: shouldAnimate ? strokeDuration : 0,
                        ease: "linear",
                      },
                    }}
                  />
                </svg>
              </span>
            </span>
          );
        }}
      />
    </CheckboxPrimitive.Root>
  );
};

```

## Credits [#credits]

* [shadcn/ui](https://ui.shadcn.com/docs/components/base/checkbox): Adapted Base Nova Checkbox foundation. Vandor UI adds Motion stroke drawing and withdrawal, mixed-state morphing, and reduced-motion support.
