Octocat

Checkbox

A simple native checkbox prepared for indeterminate states

import { Checkbox } from '@/components/checkbox';

export default function CheckboxPreview() {
  return <Checkbox />;
}

Dependencies

Source Code

'use client';

import { useEffect, useRef } from 'react';

import { composeRefs } from '@/lib/compose-refs';
import { cva } from '@/lib/utils/classnames';

interface CheckboxProps
  extends Omit<React.ComponentPropsWithRef<'input'>, 'type'> {
  indeterminate?: boolean;
}

const checkboxStyle = cva({
  base: [
    'relative flex size-5 shrink-0 appearance-none items-center justify-center rounded-sm border border-border bg-background shadow-xs outline-none ring-ring transition focus-visible:ring-4 enabled:cursor-pointer enabled:not-checked:hover:border-mix-border/8',
    // checked
    'checked:enabled:hover:mix-with-accent-foreground checked:enabled:border-accent checked:enabled:bg-accent checked:enabled:hover:border-mix-accent/8',
    // indeterminate
    'indeterminate:enabled:hover:mix-with-accent-foreground indeterminate:enabled:border-accent indeterminate:enabled:bg-accent indeterminate:enabled:hover:border-mix-accent/8',
    // checked checkmark
    "before:absolute before:text-accent-foreground checked:before:font-bold checked:before:text-xs checked:before:content-['✓']",
    // indeterminate dash
    'indeterminate:before:h-0.5 indeterminate:before:w-1.5 indeterminate:before:bg-accent-foreground',
    // disabled
    'disabled:cursor-not-allowed disabled:border-foreground/5 disabled:bg-foreground/10 disabled:indeterminate:before:bg-foreground/50 disabled:checked:before:text-foreground/50',
  ],
});

const Checkbox = ({
  ref,
  indeterminate,
  className,
  ...props
}: CheckboxProps) => {
  const internalRef = useRef<HTMLInputElement>(null);

  useEffect(() => {
    if (internalRef.current) {
      internalRef.current.indeterminate = !!indeterminate;
    }
  }, [indeterminate]);

  return (
    <input
      type="checkbox"
      ref={composeRefs(ref, internalRef)}
      className={checkboxStyle({ className })}
      {...props}
    />
  );
};

export { Checkbox };

API Reference

Extends the native input element with type="checkbox".

Examples

Simple

import { Checkbox } from '@/components/checkbox';

export default function CheckboxPreview() {
  return <Checkbox />;
}

Disabled

import { Checkbox } from '@/components/checkbox';

export default function CheckboxDisabledPreview() {
  return (
    <div className="flex flex-col space-y-4">
      <Checkbox disabled />
      <Checkbox checked disabled />
    </div>
  );
}

All states

'use client';

import { Checkbox } from '@/components/checkbox';
import { Label } from '@/components/label';

export default function CheckboxAllStatesPreview() {
  return (
    <div className="flex flex-col space-y-4">
      <div className="flex items-center space-x-2">
        <Checkbox id="unchecked" />
        <Label htmlFor="unchecked">Unchecked</Label>
      </div>

      <div className="flex items-center space-x-2">
        <Checkbox id="checked" checked onChange={() => {}} />
        <Label htmlFor="checked">Checked</Label>
      </div>

      <div className="flex items-center space-x-2">
        <Checkbox id="indeterminate" indeterminate />
        <Label htmlFor="indeterminate">Indeterminate</Label>
      </div>

      <div className="flex items-center space-x-2">
        <Checkbox id="disabled" disabled />
        <Label htmlFor="disabled">Disabled</Label>
      </div>

      <div className="flex items-center space-x-2">
        <Checkbox id="disabled-checked" checked disabled />
        <Label htmlFor="disabled-checked">Checked Disabled</Label>
      </div>

      <div className="flex items-center space-x-2">
        <Checkbox id="disabled-indeterminate" indeterminate disabled />
        <Label htmlFor="disabled-indeterminate">Indeterminate Disabled</Label>
      </div>
    </div>
  );
}

Best Practices

  1. Labels:

    • Always provide clear labels
    • Make labels clickable
    • Use consistent label positioning
  2. Accessibility:

    • Ensure keyboard navigation
    • Use fieldset for groups

Previous

Calendar

Next

Color Picker