Skip to contentVibraUI
Inputs & filters

Checkbox

A square check on the card plane that fills in the accent when it is ticked.

Vibra puts the box on the card plane with its --input boundary in both modes, where shadcn's is transparent and translucent in the dark, gives it the kit's solid focus outline, and fades the check in and out over --duration-fast instead of letting it pop. Ticked, it fills in the accent through --primary; mixed, it fills the same way and shows a dash. Disabled, the box and a Label beside it dim through Base UI's data-disabled — the box is a span, which is never :disabled.

Install

npx shadcn@latest add @vibra/checkbox

Needs the @vibra registry in your components.json — set it up once.

Examples

Side by side

Short options in a row that wraps on a phone. Each label shows the short day and says the whole one.

Disabled, with a reason

Two boxes that can't change, for two reasons, each said under its box. The box and label dim; the reason doesn't.

Required, with an error

"Required" is in the label. Pressing on without the box puts the focus on it, marked invalid and described by what to do.

At least one

A rule for the group: exporting with nothing ticked marks every box invalid with one message and moves the focus to the first.

With a link in the label

The document opens without ticking the box and says it opens a new tab; the action waits, focusable, until it is ticked.

Select all

The parent box is mixed while some rows are chosen. Chosen rows are a tint, and the action counts what it will do.

Reveals a field

Ticking the box opens the field it needs, right under it; the box says what it controls and whether it is open.

Choice cards

Several may be chosen, so each card holds a checkbox named by its title and described by its line.

With faces

People to choose, each row a label, the face hidden beside the name and the box at the row's end.

As a to-do

A done task is struck through for the eye; the box is what says it is done, and the count is a status.

Right to left

An Arabic group with each box at the right of its label and the parent box mixed while some are ticked.

Props

PropTypeDefaultDescription
checked / defaultCheckedboolean—Ticked or not, controlled or not.
onCheckedChange(checked: boolean) => void—Called with the new state when the box is pressed or its label is clicked.
indeterminatebooleanfalseThe mixed state of a parent box while only some of its children are ticked: aria-checked="mixed", filled, with a dash.
disabledbooleanfalseOut of the Tab order and dimmed, with a Label beside it; say why in text joined with aria-describedby.
requiredbooleanfalseThe box must be ticked before its form submits. Say "required" in the label too.

Dependencies

Source

components/ui/checkbox.tsx
"use client"

import { Checkbox as CheckboxPrimitive } from "@base-ui/react/checkbox"
import { cn } from "@/lib/utils"
import { CheckIcon, MinusIcon } from "lucide-react"

function Checkbox({ className, indeterminate, ...props }: CheckboxPrimitive.Root.Props) {
  return (
    <CheckboxPrimitive.Root
      data-slot="checkbox"
      indeterminate={indeterminate}
      // Base UI renders a span, which is never :disabled — the disabled look
      // answers data-disabled. A mixed box fills like a checked one and says
      // so with a dash: Base UI marks it data-indeterminate, not data-checked.
      className={cn(
        "peer relative flex size-4 shrink-0 items-center justify-center rounded-[4px] border border-input bg-card transition-colors focus-ring group-has-disabled/field:opacity-50 group-has-[:focus-visible]/field-label:ring-0 group-has-[:focus-visible]/field-label:not-data-checked:border-input after:absolute after:-inset-x-3 after:-inset-y-2 data-disabled:cursor-not-allowed data-disabled:opacity-50 aria-invalid:border-destructive aria-invalid:aria-checked:border-primary 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 group-has-[:focus-visible]/field-label:data-checked:border-primary",
        className
      )}
      {...props}
    >
      <CheckboxPrimitive.Indicator
        data-slot="checkbox-indicator"
        className="grid place-content-center text-current transition-opacity duration-(--duration-fast) ease-(--ease-standard) data-unchecked:opacity-0 [&>svg]:size-3.5"
      >
        {indeterminate ? <MinusIcon /> : <CheckIcon />}
      </CheckboxPrimitive.Indicator>
    </CheckboxPrimitive.Root>
  )
}

export { Checkbox }