Checkbox
Tactile checkbox control with yellow check fill.
components/ui/checkbox.tsx
import * as React from 'react'
import { motion, AnimatePresence } from 'motion/react'
import { Check } from 'lucide-react'
import { cn } from '@/lib/utils'
export interface CheckboxProps
extends React.InputHTMLAttributes<HTMLInputElement> {
onCheckedChange?: (checked: boolean) => void
label?: string
}
const Checkbox = React.forwardRef<HTMLInputElement, CheckboxProps>(
({ className, checked, onCheckedChange, label, ...props }, ref) => {
const [isChecked, setIsChecked] = React.useState(!!checked)
React.useEffect(() => {
if (checked !== undefined) {
setIsChecked(checked)
}
}, [checked])
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setIsChecked(e.target.checked)
onCheckedChange?.(e.target.checked)
}
return (
<label className="inline-flex items-center gap-3 cursor-pointer select-none group">
<div className="relative shrink-0">
<input
type="checkbox"
ref={ref}
checked={isChecked}
onChange={handleChange}
className="sr-only"
{...props}
/>
<motion.div
whileHover={{ scale: 1.05, x: -1, y: -1 }}
whileTap={{ scale: 0.95 }}
className={cn(
'w-6 h-6 rounded-lg rk-border-sm transition-colors duration-150 flex items-center justify-center rk-shadow-sm shrink-0 aspect-square p-0',
isChecked
? 'bg-rk-primary text-rk-ink'
: 'bg-rk-surface text-transparent group-hover:bg-rk-canvas',
className
)}
>
<AnimatePresence>
{isChecked && (
<motion.div
initial={{ scale: 0, rotate: -45 }}
animate={{ scale: 1, rotate: 0 }}
exit={{ scale: 0, rotate: 45 }}
transition={{ type: 'spring', stiffness: 500, damping: 25 }}
>
<Check className="w-4 h-4 stroke-[3.5] text-rk-ink" />
</motion.div>
)}
</AnimatePresence>
</motion.div>
</div>
{label && <span className="font-extrabold text-sm text-rk-ink">{label}</span>}
</label>
)
}
)
Checkbox.displayName = 'Checkbox'
export { Checkbox }
No selection tags yet — empty means no evidence, not lack of support.
DependenciesBundle weight · light
- icons
- lucide-react