Checkbox
Headless components for building accessible checkbox elements.
Features
- Works with native
<form>
- Full keyboard navigation.
- Can be controlled or uncontrolled.
- Can be fully rendered on the server (no client-side code)
- Indeterminate support (requires “use client”)
Basic Example
You don’t need to worry about handling the positioning or accessibility of the elements. Just focus on styling them as needed.
import { defineCheckbox } from 'crustack/checkbox'import { cn } from 'crustack/utils'import { FaCheck } from 'react-icons/fa6'
const checkbox = defineCheckbox({ hitboxPadding: '0.5rem', // the clickable area is 0.5rem larger than the checkbox cursorStyle: 'pointer',})
type Props = React.ComponentPropsWithoutRef<'input'>
export function Checkbox({ className, style, ...props }: Props) { return ( <checkbox.Root className={cn(className, 'size-5')} style={style}> {/* Hidden input, spread to props here */} {/* hitboxPadding & cursorStyle are applied on this element */} <checkbox.HiddenInput {...props} className="peer" />
{/* The visible part */} {/* `checkbox.Box` size is 100% of `checkbox.Root` size */} <checkbox.Box className={cn( // base styles 'pointer-events-none rounded border border-current transition-all [&_svg]:scale-0', // hover styles 'peer-hover:bg-base-200', // focus visible styles 'peer-focus-visible:outline peer-focus-visible:outline-2 peer-focus-visible:outline-current', // invalid styles 'peer-aria-invalid:text-error', // disabled styles 'peer-disabled:opacity-50 peer-disabled:grayscale', // checked styles 'peer-checked:text-teal-500 peer-checked:[&_svg]:scale-100', )} > {/* `checkbox.Icon` positions it's children properly */} <checkbox.Icon> <FaCheck className="size-3/5 transition-all" /> </checkbox.Icon> </checkbox.Box> </checkbox.Root> )}
With inderterminate
"use client"import { defineCheckbox } from 'crustack/checkbox'import { cn } from 'crustack/utils'import { FaCheck } from 'react-icons/fa6'
const checkbox = defineCheckbox({ hitboxPadding: '0.5rem', // the clickable area is 0.5rem larger than the checkbox})
type Props = React.ComponentPropsWithoutRef<'input'>type Props = React.ComponentPropsWithoutRef<'input'> & { checked: boolean | 'indeterminate'}
export function Checkbox({ className, style ...props }: Props) { const { checked, ref} = checkbox.useIndeterminate(props.checked)
return ( <checkbox.Root className={cn(className, 'size-5')} style={style}> {/* Hidden input, spread to props here */} {/* hitboxPadding & cursorStyle are applied on this element */} <checkbox.HiddenInput {...props} className="peer" /> <checkbox.HiddenInput {...props} className="peer" checked={checked} ref={ref} />
{/* The visible part */} {/* `checkbox.Box` size is 100% of `checkbox.Root` size */} <checkbox.Box className={cn( // base styles 'pointer-events-none rounded border border-current transition-all [&_svg]:scale-0', // hover styles 'peer-hover:bg-base-200', // focus visible styles 'peer-focus-visible:outline peer-focus-visible:outline-2 peer-focus-visible:outline-current', // invalid styles 'peer-aria-invalid:text-error', // disabled styles 'peer-disabled:opacity-50 peer-disabled:grayscale', // indeterminate styles 'peer-indeterminate:...', // checked styles 'peer-checked:text-teal-500 peer-checked:[&_svg]:scale-100', )}> {/* `checkbox.Icon` positions it's children properly */} <checkbox.Icon> <FaCheck className="size-3/5 transition-all" /> </checkbox.Icon> </checkbox.Box> </checkbox.Root> )}
API Reference
defineCheckbox
1st argument | type | default | description |
---|---|---|---|
config.hitboxPadding | string | number | 0 | Enhance the accessibility of the checkbock by increasing its hitbox. |
config.cursorStyle | string | false | 'pointer' | The cursor style of the checkbox, applied to the hitbox area. |
checkbox.Root
The top-level element, which accepts the same props as a standard <div>
.
Sizing this element will automatically size all child elements.
checkbox.HiddenInput
The visually hidden input element, which accepts the same props as a standard <input>
.
Styling is handled automatically, so you don’t need to apply any additional styles.
checkbox.Box
The visual part of the checkbox, which accepts the same props as a standard <div>
.
For proper styling, it should be the next sibling of the HiddenInput
.
Its size is inherited from the Root
element.
checkbox.Icon
The container for the icon of the checkbox, which accepts the same props as a standard <div>
.
It automatically centers its child within the Box
element.
checkbox.useIndeterminate
A utility hook for managing the indeterminate state of the checkbox.
Arguments
1st argument | type | description |
---|---|---|
checked | boolean | 'indeterminate' | The current state of the checkbox |
Returns
These values should be passed as props to the HiddenInput
:
returns | description |
---|---|
ref | A reference to attach to the HiddenInput |
checked | The state of the HiddenInput |