Choice
A radio group for single_choice or a checkbox group for multiple_choice.
Options as a vertical list of bordered cards, each with a radio or checkbox that Kibble draws (.kb-check), so it follows the theme in light and dark. Field renders it for single_choice (one value) and multiple_choice (an array).
Preview
Usage
// { key: 'timeline', type: 'single_choice', label: 'When do you want to start?', options: ['This month', 'This quarter', 'Just looking'], required: true }
import { Form } from 'kibbleforms';
<Form schema={demo} client={kibble} />
// by hand:
import { Choice } from 'kibbleforms';
<Choice name="timeline" optionClassName="py-2" />
Props
| Prop | Type | Default | Description |
|---|---|---|---|
name |
string |
required | The field key. |
className |
string |
flex flex-col gap-2 |
Merged onto the group element. |
optionClassName |
string |
option card classes | Merged onto every option <label>. |
…<div> attributes |
Spread on the group element (except onChange). |
Rendered structure:
<div id="…" role="radiogroup|group" tabindex="-1" aria-labelledby="…-label" data-kb="choice" [data-multiple]>
<label for="…-0" data-kb="option" [data-checked]>
<input id="…-0" type="radio|checkbox" class="kb-check" name="timeline" value="This month" />
<span>This month</span>
</label>
…
</div>
The group element carries the field id, so error focus lands on it, and data-multiple is present for checkbox groups. multiple_choice honours minSelected/maxSelected on submit.
Variants and states
Checkbox group:
// { key: 'channels', type: 'multiple_choice', label: 'How did you hear about us?', help: 'Pick up to two.', options: ['Search', 'A friend', 'Social media', 'Newsletter'], maxSelected: 2 }
<Form schema={survey} client={kibble} />
- Checked: the option card gets
data-checkedand a--kb-ringborder (has-[:checked]:border-kb-ring). - Hover: cards tint with
--kb-muted. - Error: the group gets
aria-invalid; the message renders below the group. - Prefill:
defaultValue: 'This month'or, for multiple,defaultValue: ['Search'].
Customization
- Tokens:
--kb-border,--kb-bg,--kb-muted(hover),--kb-ring(checked border and focus ring),--kb-input-bgand--kb-border(the unchecked box),--kb-primaryand--kb-primary-fg(the checked box and its check or dot),--kb-radius. - Horizontal options:
className="flex-row flex-wrap". Pills:optionClassName="rounded-full py-1.5". - CSS hooks:
[data-kb="choice"],[data-kb="choice"][data-multiple],[data-kb="option"][data-checked], and the box itself:.kb-check,.kb-check:checked,.kb-check::before(the check or dot). The box is centered on the first line of its label. - The whole option card is the
<label>, so clicking anywhere on it toggles the input.
Accessibility
role="radiogroup" (single) or role="group" (multiple) labelled by the field label through aria-labelledby; help and error through aria-describedby; each option is a real <input> with its own <label>, so arrow keys (radios) and Space work natively and screen readers announce "1 of 3".