Checkbox

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

Installation

bash
npx @smicolon/fasla-ui@latest add checkbox

Run init once per project before adding components. See Installation.

Preview

Variants

Two types, as in Figma. default is the box and its label on their own. layout wraps them in a bordered card that is all one click target, and fills on hover.

default
layout

Sizes

Three sizes. md is the default. The box is 16, 20 or 24px, and the label stays the same size.

default
layout

States

Checked shows a tick, indeterminate shows a dash, and neither flips in right-to-left. Hover and focus are real CSS states rather than props. Hover a checkbox, or tab to it, to see them. Disabled dims the whole checkbox, label and description included.

default
layout

Accessibility

  • Renders a native <input type="checkbox">, so Space toggles it and it submits with a form without extra code.
  • indeterminate sets the input's own property, so a screen reader announces the checkbox as mixed. A click clears it, as in the browser; keep the prop in step from your state.
  • The checkbox is named by label alone, and description is linked with aria-describedby. The box is hidden from assistive technology.
  • Without a label, pass an aria-label.
  • Direction is inherited from dir, never a prop. The box sits at the start of the line in both directions.

Props

PropTypeDefaultDescription
variant"default" | "layout""default"The box on its own, or inside a bordered card.
size"sm" | "md" | "lg""md"Checkbox size.
checkedboolean—Checked state. Use it with onChange.
defaultCheckedbooleanfalseStarting state when you do not control it.
indeterminatebooleanfalseShows a dash for a partly selected group.
disabledbooleanfalseDisables the checkbox and dims it.
labelstring—Label text.
descriptionstring—Secondary line below the label.
namestring—Name submitted with the form.
valuestring—Value submitted when checked.
onChange(event) => void—Called when the checkbox changes.
classNamestring—Extra classes for the component.

Usage

Basic checkbox

tsx
"use client"

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

export function TermsCheckbox() {
  return <Checkbox name="terms" aria-label="Accept terms and conditions" />
}

With label

tsx
"use client"

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

export function TermsCheckbox() {
  return <Checkbox name="terms" label="Accept terms and conditions" />
}

With description

tsx
"use client"

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

export function MarketingCheckbox() {
  return (
    <Checkbox
      name="marketing"
      label="Marketing emails"
      description="Receive emails about new products and features"
    />
  )
}

Layout card

tsx
"use client"

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

export function SecurityCheckbox() {
  return <Checkbox name="security" variant="layout" label="Security alerts" />
}

Different sizes

tsx
"use client"

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

export function CheckboxSizes() {
  return (
    <div className="flex flex-col gap-3">
      <Checkbox size="sm" label="Small" />
      <Checkbox size="md" label="Medium" />
      <Checkbox size="lg" label="Large" />
    </div>
  )
}

Select all, with an indeterminate parent

tsx
"use client"

import { useState } from "react"
import { Checkbox } from "@/components/ui/checkbox"

const files = ["Annual report.pdf", "Invoices.xlsx", "Contract.docx"]

export function SelectAllFiles() {
  const [selected, setSelected] = useState<string[]>([])
  const all = selected.length === files.length

  return (
    <div className="flex flex-col gap-3">
      <Checkbox
        label="Select all files"
        checked={all}
        indeterminate={selected.length > 0 && !all}
        onChange={(e) => setSelected(e.target.checked ? files : [])}
      />
      <div className="flex flex-col gap-3 ps-8">
        {files.map((file) => (
          <Checkbox
            key={file}
            size="sm"
            label={file}
            checked={selected.includes(file)}
            onChange={(e) =>
              setSelected((prev) =>
                e.target.checked ? [...prev, file] : prev.filter((f) => f !== file)
              )
            }
          />
        ))}
      </div>
    </div>
  )
}

Files selected: 1