bottega

primitives

Neumorphic Checkbox

A circular neumorphic checkbox that pairs a visually-hidden native <input type="checkbox"> with a custom decorative mark. At rest the disc renders as a soft muted surface; hovering applies an inset neumorphic shadow (dual-tone foreground/background alpha composites) to create a pressed-in concavity. Checking triggers a full rotateX(360deg) CSS flip that reveals a white tick and transitions the disc to the primary accent color, snapping the inset shadow away. All shadows are derived from --foreground and --background alpha channels so the effect reads correctly under both light and dark themes. Fully keyboard-accessible: the hidden input receives :focus-visible which lifts a --ring outline onto the decorative mark via a sibling combinator.

Ported from Praashoo7 (MIT)


Install

1. Register the namespace (once per project):

json
// components.json — register the @bottega namespace once
{
  "registries": {
    "@bottega": { "url": "https://bottega.ariacode.ca/r/{name}.json" }
  }
}

2. Add the component:

bash
npx shadcn add @bottega/neumorphic-checkbox

Usage

Usagetsx
<NeumorphicCheckbox />

Props

PropTypeDefaultDescription
labelstringVisible label for the checkbox.
checkedbooleanControlled checked state.
defaultCheckedbooleanDefault checked state (uncontrolled).
onChange(checked: boolean) => voidChange handler — receives the new boolean state.
disabledbooleanfalseDisables the checkbox when true.
classNamestringAdditional class name merged onto the root <label>.
idstringUnique id for the <input>; generated from label when omitted.

Source

neumorphic-checkbox.tsxtsx
import type React from "react";
import styles from "./neumorphic-checkbox.module.css";

export type NeumorphicCheckboxProps = {
  /** Visible label for the checkbox. */
  label?: string;
  /** Controlled checked state. */
  checked?: boolean;
  /** Default checked state (uncontrolled). */
  defaultChecked?: boolean;
  /** Change handler — receives the new boolean state. */
  onChange?: (checked: boolean) => void;
  /** Disables the checkbox when true. */
  disabled?: boolean;
  /** Additional class name merged onto the root <label>. */
  className?: string;
  /** Unique id for the <input>; generated from label when omitted. */
  id?: string;
};

/**
 * NeumorphicCheckbox — a circular neumorphic checkbox with a 3-D inset
 * hover shadow and a rotateX flip-reveal tick on check.
 */
export function NeumorphicCheckbox({
  label,
  checked,
  defaultChecked,
  onChange,
  disabled = false,
  className = "",
  id,
}: NeumorphicCheckboxProps) {
  const inputId =
    id ?? (label ? `ncb-${label.toLowerCase().replace(/\s+/g, "-")}` : "ncb");

  function handleChange(e: React.ChangeEvent<HTMLInputElement>) {
    onChange?.(e.currentTarget.checked);
  }

  const rootClass = [styles.container, disabled && styles.disabled, className]
    .filter(Boolean)
    .join(" ");

  return (
    <label htmlFor={inputId} className={rootClass}>
      {/* Visually-hidden native input carries all state + a11y */}
      <input
        id={inputId}
        type="checkbox"
        className={styles.input}
        checked={checked}
        defaultChecked={defaultChecked}
        onChange={handleChange}
        disabled={disabled}
      />
      {/* Decorative custom mark — aria-hidden because the input is the control */}
      <span className={styles.checkmark} aria-hidden="true">
        <span className={styles.tick} aria-hidden="true" />
      </span>
      {label && <span className={styles.label}>{label}</span>}
    </label>
  );
}

Dependencies

  • @bottega/tokens