Checkbox

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

The Checkbox component is a control that allows users to toggle between checked and not checked states. It features bold borders and shadows that match the Things design system. Built from scratch using React and native HTML input elements. No dependencies on any UI library.

Code

TypeScript: Copy this code into components/ui/checkbox.tsx:

tsx
"use client"

import * as React from "react"
import { cn } from "@/lib/utils"

export interface CheckboxProps
  extends Omit<React.InputHTMLAttributes<HTMLInputElement>, "type"> {}

const Checkbox = React.forwardRef<HTMLInputElement, CheckboxProps>(
  ({ className, checked, onChange, disabled, ...props }, ref) => {
    const [isChecked, setIsChecked] = React.useState(checked ?? false)

    React.useEffect(() => {
      if (checked !== undefined) {
        setIsChecked(checked)
      }
    }, [checked])

    const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
      if (disabled) return
      setIsChecked(e.target.checked)
      onChange?.(e)
    }

    return (
      <label
        className={cn(
          "relative inline-flex h-5 w-5 items-center justify-center cursor-pointer",
          disabled && "opacity-50 cursor-not-allowed",
          className
        )}
      >
        <input
          type="checkbox"
          className="sr-only"
          checked={isChecked}
          onChange={handleChange}
          disabled={disabled}
          ref={ref}
          {...props}
        />
        <span
          className={cn(
            "flex h-5 w-5 items-center justify-center rounded-sm border-2 border-foreground bg-background transition-colors neobrutalism-shadow-sm",
            isChecked && "bg-primary border-primary",
            disabled && "cursor-not-allowed"
          )}
        >
          {isChecked && (
            <svg
              className="h-3 w-3 text-primary-foreground"
              fill="none"
              stroke="currentColor"
              viewBox="0 0 24 24"
              strokeWidth={3}
            >
              <path
                strokeLinecap="round"
                strokeLinejoin="round"
                d="M5 13l4 4L19 7"
              />
            </svg>
          )}
        </span>
      </label>
    )
  }
)
Checkbox.displayName = "Checkbox"

export { Checkbox }

JavaScript: Copy this code into components/ui/checkbox.jsx:

jsx
"use client"

import * as React from "react"
import { cn } from "@/lib/utils"

const Checkbox = React.forwardRef(
  ({ className, checked, onChange, disabled, ...props }, ref) => {
    const [isChecked, setIsChecked] = React.useState(checked ?? false)

    React.useEffect(() => {
      if (checked !== undefined) {
        setIsChecked(checked)
      }
    }, [checked])

    const handleChange = (e) => {
      if (disabled) return
      setIsChecked(e.target.checked)
      onChange?.(e)
    }

    return (
      <label
        className={cn(
          "relative inline-flex h-5 w-5 items-center justify-center cursor-pointer",
          disabled && "opacity-50 cursor-not-allowed",
          className
        )}
      >
        <input
          type="checkbox"
          className="sr-only"
          checked={isChecked}
          onChange={handleChange}
          disabled={disabled}
          ref={ref}
          {...props}
        />
        <span
          className={cn(
            "flex h-5 w-5 items-center justify-center rounded-sm border-2 border-foreground bg-background transition-colors neobrutalism-shadow-sm",
            isChecked && "bg-primary border-primary",
            disabled && "cursor-not-allowed"
          )}
        >
          {isChecked && (
            <svg
              className="h-3 w-3 text-primary-foreground"
              fill="none"
              stroke="currentColor"
              viewBox="0 0 24 24"
              strokeWidth={3}
            >
              <path
                strokeLinecap="round"
                strokeLinejoin="round"
                d="M5 13l4 4L19 7"
              />
            </svg>
          )}
        </span>
      </label>
    )
  }
)
Checkbox.displayName = "Checkbox"

export { Checkbox }

Usage

TypeScript:

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

function MyComponent() {
  const [checked, setChecked] = useState(false)
  
  return (
    <Checkbox 
      checked={checked} 
      onChange={(e) => setChecked(e.target.checked)} 
    />
  )
}

JavaScript:

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

function MyComponent() {
  const [checked, setChecked] = useState(false)
  
  return (
    <Checkbox 
      checked={checked} 
      onChange={(e) => setChecked(e.target.checked)} 
    />
  )
}

Make sure you also have the lib/utils.ts file with the cn helper function.

Examples