) => {
onChange?.(e.target.checked);
},
[onChange]
);
// Controlled vs uncontrolled mode
const isControlled = checked !== undefined;
const checkedProp = isControlled ? { checked } : {};
const defaultCheckedProp = !isControlled && defaultChecked !== undefined
? { defaultChecked }
: {};
// Dev-only validation: Warn if switching between controlled/uncontrolled
// This helps catch common React bugs where state management changes mid-lifecycle
const wasControlledRef = React.useRef(isControlled);
React.useEffect(() => {
if (process.env.NODE_ENV === 'development') {
if (wasControlledRef.current !== isControlled) {
// eslint-disable-next-line no-console
console.warn(
`Checkbox with id="${id}" is changing from ${
wasControlledRef.current ? 'controlled' : 'uncontrolled'
} to ${
isControlled ? 'controlled' : 'uncontrolled'
}. This is likely a bug. ` +
`Decide between using "checked" (controlled) or "defaultChecked" (uncontrolled) and stick with it.`
);
}
wasControlledRef.current = isControlled;
}
}, [isControlled, id]);
// Note: No need to manage disabled class - CSS uses :has() selector with aria-disabled
// The Input component handles aria-disabled automatically via useDisabledState hook
return (
);
}
);
Checkbox.displayName = "Checkbox";
export default Checkbox;