Checkbox Field

Checkbox component integrated with TanStack Form via React context for boolean field management.

npx shadcn@latest add @shuip/tsf-checkbox-field
pnpm dlx shadcn@latest add @shuip/tsf-checkbox-field
bun x shadcn@latest add @shuip/tsf-checkbox-field
'use client';

import { Checkbox } from '@/components/ui/checkbox';
import { Field, FieldDescription, FieldError, FieldLabel } from '@/components/ui/field';
import { useFieldContext } from '@/components/ui/shuip/tanstack-form/form-context';

export interface CheckboxFieldProps {
  label: string;
  description?: string;
  fieldProps?: React.ComponentProps<typeof Field>;
  props?: React.ComponentProps<typeof Checkbox>;
}

export function CheckboxField({ label, description, fieldProps, props }: CheckboxFieldProps) {
  const field = useFieldContext<boolean>();
  const { isValid, errors } = field.state.meta;

  return (
    <Field className='gap-2' data-invalid={!isValid} {...fieldProps}>
      <div className='flex items-center gap-2'>
        <Checkbox
          id={field.name}
          name={field.name}
          checked={field.state.value}
          onCheckedChange={(checked) => field.handleChange(checked === true)}
          onBlur={field.handleBlur}
          aria-invalid={!isValid}
          {...props}
        />
        <FieldLabel htmlFor={field.name} className='text-sm cursor-pointer'>
          {label}
        </FieldLabel>
      </div>
      {!isValid && (
        <FieldError
          className='text-xs text-left'
          errors={errors.map((error) => ({ message: typeof error === 'string' ? error : error?.message }))}
        />
      )}
      {description && <FieldDescription className='text-xs'>{description}</FieldDescription>}
    </Field>
  );
}

The command is the same for Base UI and Radix: the CLI reads the style in your components.json and installs the matching variant.

Loading...

Checkboxes are binary inputs for opting in or accepting terms. CheckboxField combines Radix UI's Checkbox with an inline clickable label, making the entire label area interactive — not just the checkbox itself.

The component reads the surrounding field via useFieldContext, so you compose it inside a <form.AppField>. Common patterns include required checkboxes for terms acceptance (validated with !value check) or grouped checkboxes for multi-select options (using nested field paths like features.notifications).

Built-in features

  • Context-bound field state via useFieldContext — no prop drilling
  • Clickable inline label positioned next to checkbox
  • Boolean field type for true/false state
  • Required validation for terms acceptance
  • Nested paths for grouped checkboxes

Setup

Field components are bound via React context. In your project, create lib/form.ts once:

// lib/form.ts
import { createFormHook } from '@tanstack/react-form';
import { fieldContext, formContext } from '@/components/ui/shuip/tanstack-form/form-context';
import { CheckboxField } from '@/components/ui/shuip/tanstack-form/checkbox-field';
import { SubmitButton } from '@/components/ui/shuip/tanstack-form/submit-button';

export const { useAppForm } = createFormHook({
  fieldContext,
  formContext,
  fieldComponents: { CheckboxField },
  formComponents: { SubmitButton },
});

See the form-context item for details.

Required checkbox

import { useAppForm } from '@/lib/form';

const form = useAppForm({
  defaultValues: { terms: false },
  onSubmit: async ({ value }) => {
    await saveData(value);
  },
});

<form.AppField
  name='terms'
  validators={{
    onChange: ({ value }) =>
      !value ? 'You must accept the terms' : undefined,
  }}
  children={(field) => (
    <field.CheckboxField label='I accept the terms and conditions' />
  )}
/>

Examples

Dependent Fields

Loading...
'use client';

import { createFormHook, useStore } from '@tanstack/react-form';
import { CheckboxField } from '@/components/ui/shuip/tanstack-form/checkbox-field';
import { fieldContext, formContext } from '@/components/ui/shuip/tanstack-form/form-context';
import { InputField } from '@/components/ui/shuip/tanstack-form/input-field';
import { SubmitButton } from '@/components/ui/shuip/tanstack-form/submit-button';

const { useAppForm } = createFormHook({
  fieldContext,
  formContext,
  fieldComponents: { CheckboxField, InputField },
  formComponents: { SubmitButton },
});

export default function TsfCheckboxFieldDependentFieldsExample() {
  const form = useAppForm({
    defaultValues: {
      customShipping: false,
      shippingAddress: '',
      sameAsBilling: false,
      billingAddress: '',
    },
    onSubmit: async ({ value }) => {
      await new Promise((resolve) => setTimeout(resolve, 1000));
      alert(JSON.stringify(value, null, 2));
    },
  });

  const customShipping = useStore(form.store, (state) => state.values.customShipping);
  const sameAsBilling = useStore(form.store, (state) => state.values.sameAsBilling);

  return (
    <form
      onSubmit={(e) => {
        e.preventDefault();
        form.handleSubmit();
      }}
      className='space-y-4'
    >
      <form.AppField
        name='billingAddress'
        validators={{
          onChange: ({ value }) => (!value ? 'Billing address is required' : undefined),
        }}
        children={(field) => <field.InputField label='Billing Address' />}
      />

      <form.AppField
        name='customShipping'
        listeners={{
          onChange: ({ value }) => {
            if (!value) {
              form.setFieldValue('shippingAddress', '');
              form.setFieldValue('sameAsBilling', false);
            }
          },
        }}
        children={(field) => <field.CheckboxField label='Use a different shipping address' />}
      />

      {customShipping && (
        <div className='space-y-4 pl-6 border-l-2'>
          <form.AppField
            name='sameAsBilling'
            listeners={{
              onChange: ({ value }) => {
                if (!value) {
                  form.setFieldValue('shippingAddress', '');
                } else {
                  form.setFieldValue('shippingAddress', form.getFieldValue('billingAddress'));
                }
              },
            }}
            children={(field) => <field.CheckboxField label='Same as billing address' />}
          />

          <form.AppField
            name='shippingAddress'
            validators={{
              onChange: ({ value }) => {
                if (!value && customShipping) return 'Shipping address is required';
                return undefined;
              },
            }}
            children={(field) => <field.InputField label='Shipping Address' props={{ disabled: sameAsBilling }} />}
          />
        </div>
      )}

      <form.AppForm>
        <form.SubmitButton>Submit Order</form.SubmitButton>
      </form.AppForm>
    </form>
  );
}

Default

Loading...
'use client';

import { createFormHook } from '@tanstack/react-form';
import { CheckboxField } from '@/components/ui/shuip/tanstack-form/checkbox-field';
import { fieldContext, formContext } from '@/components/ui/shuip/tanstack-form/form-context';
import { SubmitButton } from '@/components/ui/shuip/tanstack-form/submit-button';

const { useAppForm } = createFormHook({
  fieldContext,
  formContext,
  fieldComponents: { CheckboxField },
  formComponents: { SubmitButton },
});

export default function TsfCheckboxFieldExample() {
  const form = useAppForm({
    defaultValues: {
      termsExample: false,
      marketingExample: false,
    },
    onSubmit: async ({ value }) => {
      await new Promise((resolve) => setTimeout(resolve, 1000));
      alert(JSON.stringify(value, null, 2));
    },
  });

  return (
    <form
      onSubmit={(e) => {
        e.preventDefault();
        form.handleSubmit();
      }}
      className='space-y-4'
    >
      <form.AppField
        name='termsExample'
        validators={{
          onChange: ({ value }) => (!value ? 'You must accept the terms and conditions' : undefined),
        }}
        children={(field) => <field.CheckboxField label='I accept the terms and conditions' />}
      />

      <form.AppField
        name='marketingExample'
        children={(field) => (
          <field.CheckboxField
            label='Send me promotional emails'
            description='Optional: Receive updates about new features'
          />
        )}
      />

      <form.AppForm>
        <form.SubmitButton />
      </form.AppForm>
    </form>
  );
}

Group

Loading...
'use client';

import { createFormHook } from '@tanstack/react-form';
import { CheckboxField } from '@/components/ui/shuip/tanstack-form/checkbox-field';
import { fieldContext, formContext } from '@/components/ui/shuip/tanstack-form/form-context';
import { SubmitButton } from '@/components/ui/shuip/tanstack-form/submit-button';

const { useAppForm } = createFormHook({
  fieldContext,
  formContext,
  fieldComponents: { CheckboxField },
  formComponents: { SubmitButton },
});

export default function TsfCheckboxFieldGroupExample() {
  const form = useAppForm({
    defaultValues: {
      features: {
        notifications: false,
        analytics: false,
        darkMode: false,
        apiAccess: false,
      },
    },
    onSubmit: async ({ value }) => {
      await new Promise((resolve) => setTimeout(resolve, 1000));
      alert(JSON.stringify(value, null, 2));
    },
  });

  return (
    <form
      onSubmit={(e) => {
        e.preventDefault();
        form.handleSubmit();
      }}
      className='space-y-4'
    >
      <div className='space-y-3'>
        <h3 className='font-semibold'>Features</h3>
        <p className='text-sm text-muted-foreground'>Select the features you want to enable</p>

        <div className='space-y-3'>
          <form.AppField
            name='features.notifications'
            children={(field) => (
              <field.CheckboxField
                label='Enable push notifications'
                description='Receive real-time updates about your activity'
              />
            )}
          />

          <form.AppField
            name='features.analytics'
            children={(field) => (
              <field.CheckboxField
                label='Enable analytics tracking'
                description='Help us improve by sharing usage data'
              />
            )}
          />

          <form.AppField
            name='features.darkMode'
            children={(field) => (
              <field.CheckboxField label='Enable dark mode' description='Switch to a darker color scheme' />
            )}
          />

          <form.AppField
            name='features.apiAccess'
            children={(field) => (
              <field.CheckboxField label='Enable API access' description='Get programmatic access to your data' />
            )}
          />
        </div>
      </div>

      <form.AppForm>
        <form.SubmitButton>Save Preferences</form.SubmitButton>
      </form.AppForm>
    </form>
  );
}

Props

Prop

Type

On this page