A form control that allows the user to toggle between checked and not checked. Supports indeterminate state.
Pair with Field.* for label/description/error wiring in forms:
You must accept before continuing.
1import { Checkbox } from "@ngrok/mantle/checkbox";2import { Field } from "@ngrok/mantle/field";3 4<Field.Item name="terms-of-service">5 <Field.Label className="flex items-center gap-2">6 <Field.Control>7 <Checkbox />8 </Field.Control>9 Accept terms and conditions10 </Field.Label>11 <Field.Description>You must accept before continuing.</Field.Description>12</Field.Item>;Or render the control on its own:
1import { Checkbox } from "@ngrok/mantle/checkbox";2import { Label } from "@ngrok/mantle/label";3 4<Label htmlFor="terms" className="flex items-center gap-2">5 <Checkbox name="terms" id="terms" />6 Accept terms and conditions7</Label>8<Label htmlFor="unchecked" className="flex items-center gap-2">9 <Checkbox id="unchecked" name="unchecked" checked={false} readOnly />10 Unchecked11</Label>12<Label htmlFor="checked" className="flex items-center gap-2">13 <Checkbox id="checked" name="checked" checked readOnly />14 Checked15</Label>16<Label htmlFor="indeterminate" className="flex items-center gap-2">17 <Checkbox id="indeterminate" name="indeterminate" defaultChecked="indeterminate" readOnly />18 Indeterminate19</Label>Use @tanstack/react-form with zod to keep the checkbox controlled and render errors through Field.Errors.
1import { Button } from "@ngrok/mantle/button";2import { Checkbox } from "@ngrok/mantle/checkbox";3import { Field, toErrorMessages } from "@ngrok/mantle/field";4import { useForm } from "@tanstack/react-form";5import { z } from "zod";6 7export const formSchema = z.object({8 acceptedTerms: z.boolean().refine((value) => value, "Accept the terms to continue."),9});10function Example() {11 const defaultValues = {12 acceptedTerms: false,13 };14 const form = useForm({15 defaultValues,16 validators: {17 onSubmit: formSchema,18 },19 onSubmit: ({ value }) => {20 // Handle form submission here21 },22 });23 24 return (25 <form26 className="space-y-4"27 onSubmit={(event) => {28 event.preventDefault();29 event.stopPropagation();30 void form.handleSubmit();31 }}32 >33 <form.Field name="acceptedTerms">34 {(field) => (35 <Field.Item name={field.name}>36 <Field.Label className="flex items-center gap-2">37 <Field.Control>38 <Checkbox39 checked={field.state.value}40 onBlur={field.handleBlur}41 onChange={(event) => field.handleChange(event.target.checked)}42 />43 </Field.Control>44 Accept terms and conditions45 </Field.Label>46 <Field.Errors messages={toErrorMessages(field.state.meta.errors)} />47 </Field.Item>48 )}49 </form.Field>50 <Button type="submit" appearance="filled" priority="neutral">51 Submit52 </Button>53 </form>54 );55}selectAllChecked resolves the tri-state checked value for a "select all" checkbox from the current selection counts — true when everything is selected, "indeterminate" when only some is, and false when nothing is. Use it to keep the header checkbox of a multi-select list correct without hand-rolling the tri-state branch. It pairs directly with a DataTable row-selection column:
1import { Checkbox, selectAllChecked } from "@ngrok/mantle/checkbox";2 3<Checkbox4 aria-label="Select all rows"5 checked={selectAllChecked({6 allSelected: table.getIsAllRowsSelected(),7 someSelected: table.getIsSomeRowsSelected(),8 })}9 onChange={(event) => table.toggleAllRowsSelected(event.target.checked)}10/>;Returns boolean | "indeterminate" (a CheckedState), ready to pass straight to Checkbox's checked prop. allSelected takes precedence, so it wins even if a caller also reports someSelected.
All props from input[type="checkbox"], plus: