Checkbox Group
A whole group of checkboxes, not a single box — value is always an array. Options wrap into rows and an inline filter narrows long lists.
'use client'
import * as React from 'react'
import { Checkbox } from '@/components/custom/checkbox'
const PERMISSIONS = [
{ code: 'payouts.read', name: 'View payouts' },
{ code: 'payouts.write', name: 'Create payouts' },
{ code: 'payouts.approve', name: 'Approve payouts' },
{ code: 'people.read', name: 'View employees' },
{ code: 'people.write', name: 'Edit employees' },
{ code: 'reports.export', name: 'Export reports' },
]
export default function CheckboxDemo() {
const [granted, setGranted] = React.useState<string[]>(['payouts.read'])
return (
<div className="w-full max-w-xl space-y-3">
<Checkbox
label="Role permissions"
required
data={PERMISSIONS}
value={granted}
onChange={setGranted}
valueKey="code"
labelKey="name"
searchPlaceholder="Filter permissions..."
error={granted.length === 0}
errorMessage="Pick at least one permission."
/>
<p className="text-sm text-muted-foreground">
Granted: <code className="font-mono">{granted.length ? granted.join(', ') : 'none'}</code>
</p>
</div>
)
}Installation
pnpm dlx shadcn@latest add https://kiotapay-ui-staging.staging.kiotapay.com/r/checkbox.jsonnpx shadcn@latest add https://kiotapay-ui-staging.staging.kiotapay.com/r/checkbox.jsonyarn dlx shadcn@latest add https://kiotapay-ui-staging.staging.kiotapay.com/r/checkbox.jsonbunx --bun shadcn@latest add https://kiotapay-ui-staging.staging.kiotapay.com/r/checkbox.jsonInstalls components/custom/checkbox.tsx, the shadcn checkbox primitive it renders each option with, and form-field. The single primitive stays available at @/components/ui/checkbox.
Usage
'use client'
import { Checkbox } from '@/components/custom/checkbox'
export function PermissionsField({ permissions, value, onChange }) {
return (
<Checkbox
label="Role permissions"
required
data={permissions}
value={value}
onChange={onChange}
valueKey="code"
labelKey="name"
/>
)
}Props
| Prop | Type | Default | Description |
|---|---|---|---|
data | FieldOption[] | [] | Options. Any object shape. |
value | any[] | [] | Selected values. |
onChange | (value: any[]) => void | — | Receives the whole next array. |
valueKey | string | 'value' | Property read for each stored value. |
labelKey | string | string[] | 'label' | Property or properties read for each label. |
label | string | — | Required. Labels the group. |
placeholder | string | 'No options available.' | Shown when the list is empty or filtered to nothing. |
searchPlaceholder | string | 'Search...' | Pass '' to hide the filter entirely. |
disabled | boolean | false | Blocks every option. |
error | boolean | false | Switches the group border to the danger ramp. |
errorMessage | ReactNode | — | Shown only while error is true. |
Behaviour notes
Options wrap rather than stack. Permission and tag labels are short, and a single-file column turns twenty of them into a scroll for no gain.
Filtering never changes the selection. Options hidden by the filter stay selected — the filter is a view over the list, not an edit to it.
The group is announced as a group. role="group" plus aria-labelledby means a screen reader reads the heading once and then each option, instead of repeating the heading per box.