Checkbox
A wrapper for Base UI Checkbox and CheckboxGroup with label/description support, select-all parent checkbox, orientation control, and controlled state.
Checkbox
Single checkbox — label + description + disabled
indeterminate — controlled tri-state
CheckboxWrapper
options — list with descriptions and disabled
parentLabel — select-all with indeterminate state
value + onValueChange — controlled group
Selected: email, sms
orientation: horizontal
CheckboxGroup primitive
Manual composition
Installation
npx shadcn@latest add @glrk-ui/checkboxIf you haven't set up the prerequisites yet, check out Prerequest section.
Copy and paste the following code into your project.
'use client'
import * as React from 'react'
import { CheckboxGroup as CheckboxGroupPrimitive } from '@base-ui/react/checkbox-group'
import { Checkbox as CheckboxPrimitive } from '@base-ui/react/checkbox'
import { CheckIcon, MinusIcon } from 'lucide-react'
import { cn, getKey, getLabel, getValue } from '@/lib/utils'
function CheckboxIndicator({ className, ...props }: CheckboxPrimitive.Root.Props) {
return (
<CheckboxPrimitive.Root
data-slot="checkbox"
className={cn(
'peer group/checkbox relative flex size-4.5 shrink-0 items-center justify-center rounded border border-input transition-colors outline-none group-has-disabled/field:opacity-50 after:absolute after:-inset-x-3 after:-inset-y-2 focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 aria-invalid:aria-checked:border-primary dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 data-checked:border-primary data-checked:bg-primary data-checked:text-primary-foreground dark:data-checked:bg-primary data-indeterminate:border-primary data-indeterminate:bg-primary data-indeterminate:text-primary-foreground dark:data-indeterminate:bg-primary',
className,
)}
{...props}
>
<CheckboxPrimitive.Indicator
data-slot="checkbox-indicator"
className="grid place-content-center text-current transition-none [&>svg]:size-3.5"
render={(indicatorProps, state) => (
<span {...indicatorProps}>
{state.indeterminate ? <MinusIcon /> : <CheckIcon />}
</span>
)}
/>
</CheckboxPrimitive.Root>
)
}
type CheckboxProps = {
label: React.ReactNode
description?: React.ReactNode
wrapperCls?: string
as?: React.ElementType
} & CheckboxPrimitive.Root.Props
function Checkbox({ label, description, wrapperCls, className, as: Comp = 'label', ...props }: CheckboxProps) {
return (
<Comp className={cn('flex cursor-pointer select-none items-start gap-2 has-[:disabled]:cursor-not-allowed', wrapperCls)}>
<CheckboxIndicator className={cn(className)} {...props} />
<p className="grid">
<span className="text-sm font-medium leading-none">{label}</span>
{description && <span className="mt-0.5 text-xs text-muted-foreground">{description}</span>}
</p>
</Comp>
)
}
function CheckboxGroup({ className, ...props }: CheckboxGroupPrimitive.Props) {
return (
<CheckboxGroupPrimitive
data-slot="checkbox-group"
className={cn('flex flex-col gap-2', className)}
{...props}
/>
)
}
type checkboxOptionT = allowedPrimitiveT | (optionT & { description?: React.ReactNode })
type CheckboxWrapperProps = {
options: checkboxOptionT[]
parentLabel?: React.ReactNode
orientation?: 'horizontal' | 'vertical'
itemCls?: string
as?: React.ElementType
} & Omit<CheckboxGroupPrimitive.Props, 'children'>
function CheckboxWrapper({
options,
parentLabel,
orientation = 'vertical',
itemCls,
className,
as,
...props
}: CheckboxWrapperProps) {
const allValues = options.map(opt => String(getValue(opt)))
return (
<CheckboxGroup
allValues={parentLabel ? allValues : undefined}
className={cn(orientation === 'horizontal' && 'flex-row flex-wrap', className)}
{...props}
>
{parentLabel && (
<Checkbox label={parentLabel} wrapperCls={itemCls} data-parent />
)}
{options.map((opt, i) => (
<Checkbox
key={getKey(opt, i)}
value={String(getValue(opt))}
label={getLabel(opt)}
description={typeof opt === 'object' ? opt.description : undefined}
disabled={typeof opt === 'object' ? opt.disabled : undefined}
wrapperCls={itemCls}
as={as}
/>
))}
</CheckboxGroup>
)
}
export {
CheckboxIndicator,
Checkbox,
CheckboxGroup,
CheckboxWrapper,
type CheckboxProps,
type checkboxOptionT,
type CheckboxWrapperProps,
}
Usage
Single checkbox — Checkbox
import { Checkbox } from "@/components/ui/checkbox"
<Checkbox label="Accept terms and conditions" />
<Checkbox
label="Email notifications"
description="Receive updates about your account."
/>
<Checkbox label="Disabled" disabled />Group — CheckboxWrapper
import { CheckboxWrapper } from "@/components/ui/checkbox"
<CheckboxWrapper
defaultValue={["email"]}
options={[
{ value: "email", label: "Email", description: "Notify via email." },
{ value: "sms", label: "SMS" },
{ value: "push", label: "Push notifications" },
{ value: "locked", label: "Cannot change", disabled: true },
]}
/>Select-all parent checkbox
Set parentLabel to add a parent checkbox that shows indeterminate when partially selected:
<CheckboxWrapper
defaultValue={["email"]}
parentLabel="All notifications"
options={[
{ value: "email", label: "Email" },
{ value: "sms", label: "SMS" },
{ value: "push", label: "Push" },
]}
/>Controlled
const [value, setValue] = useState<string[]>(["email"])
<CheckboxWrapper
value={value}
onValueChange={(v) => setValue(v)}
parentLabel="Select all"
options={[
{ value: "email", label: "Email" },
{ value: "sms", label: "SMS" },
]}
/>Horizontal orientation
<CheckboxWrapper
orientation="horizontal"
defaultValue={["react"]}
options={[
{ value: "react", label: "React" },
{ value: "vue", label: "Vue" },
{ value: "svelte", label: "Svelte" },
]}
/>Reference
checkboxOptionT
Prop
Type
Checkbox
Prop
Type
CheckboxWrapper
Prop
Type