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):
// components.json — register the @bottega namespace once
{
"registries": {
"@bottega": { "url": "https://bottega.ariacode.ca/r/{name}.json" }
}
}2. Add the component:
npx shadcn add @bottega/neumorphic-checkboxUsage
<NeumorphicCheckbox />Props
| Prop | Type | Default | Description |
|---|---|---|---|
| label | string | — | Visible label for the checkbox. |
| checked | boolean | — | Controlled checked state. |
| defaultChecked | boolean | — | Default checked state (uncontrolled). |
| onChange | (checked: boolean) => void | — | Change handler — receives the new boolean state. |
| disabled | boolean | false | Disables the checkbox when true. |
| className | string | Additional class name merged onto the root <label>. | |
| id | string | — | Unique id for the <input>; generated from label when omitted. |
Source
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