Advanced

Conditional logic

Show, hide, require, and disable fields dynamically based on the values of other fields in the form.


Overview

Every field type in Nestled Forms supports four conditional properties:

PropertyDescription
showWhenShow or hide the field
requiredWhenMake the field required or optional
disabledWhenEnable or disable the field
validateWhenEnable or skip validation

Each takes a function that receives the current form values and returns a boolean.

;(formValues: Record<string, any>) => boolean

showWhen

Conditionally show or hide a field. When the function returns false, the field is completely hidden from the form.

FormFieldClass.text('companyName', {
  label: 'Company Name',
  showWhen: (values) => values.accountType === 'business',
})

Multiple conditions

FormFieldClass.text('managerName', {
  label: 'Manager Name',
  showWhen: (values) =>
    values.accountType === 'business' && values.teamSize > 10,
})

Select-dependent fields

const fields = [
  FormFieldClass.select('contactMethod', {
    label: 'Preferred Contact Method',
    options: [
      { value: 'email', label: 'Email' },
      { value: 'phone', label: 'Phone' },
      { value: 'mail', label: 'Postal Mail' },
    ],
  }),
  FormFieldClass.email('contactEmail', {
    label: 'Email Address',
    required: true,
    showWhen: (values) => values.contactMethod === 'email',
  }),
  FormFieldClass.phone('contactPhone', {
    label: 'Phone Number',
    required: true,
    showWhen: (values) => values.contactMethod === 'phone',
  }),
  FormFieldClass.text('mailingAddress', {
    label: 'Mailing Address',
    required: true,
    showWhen: (values) => values.contactMethod === 'mail',
  }),
]

Hidden field values

When a field is hidden via showWhen, its value is still preserved in the form state. On submission, hidden field values are included unless you handle this in your submit handler.


requiredWhen

Dynamically make a field required based on other field values.

FormFieldClass.text('taxId', {
  label: 'Tax ID',
  requiredWhen: (values) => values.accountType === 'business',
})

Combined with showWhen

A common pattern is to show and require a field simultaneously:

FormFieldClass.text('companyName', {
  label: 'Company Name',
  showWhen: (values) => values.isCompany,
  requiredWhen: (values) => values.isCompany,
})

This ensures the field is both visible and required when the condition is met, and hidden and optional when it's not.


disabledWhen

Dynamically disable a field. Disabled fields are visible but not editable.

FormFieldClass.text('promoCode', {
  label: 'Promo Code',
  disabledWhen: (values) => values.plan === 'free',
  helpText: 'Promo codes only apply to paid plans',
})

Disable based on checkbox

FormFieldClass.switch('useCustomAddress', {
  label: 'Use custom shipping address',
}),
FormFieldClass.text('shippingAddress', {
  label: 'Shipping Address',
  disabledWhen: (values) => !values.useCustomAddress,
}),

validateWhen

Only validate a field when a condition is met. When the condition is false, the field skips all validation — even required checks.

FormFieldClass.text('vatNumber', {
  label: 'VAT Number',
  required: true,
  validate: (value) => /^[A-Z]{2}\d{9}$/.test(value) || 'Invalid VAT format',
  validateWhen: (values) => values.country !== 'US',
})

This is different from requiredWhenvalidateWhen controls all validation (including custom validate functions), while requiredWhen only controls the required check.


Real-world examples

Registration form with role-based fields

const registrationFields = [
  FormFieldClass.text('name', { label: 'Full Name', required: true }),
  FormFieldClass.email('email', { label: 'Email', required: true }),
  FormFieldClass.password('password', { label: 'Password', required: true }),

  FormFieldClass.radio('role', {
    label: 'Account Type',
    radioOptions: [
      { value: 'individual', label: 'Individual' },
      { value: 'business', label: 'Business' },
      { value: 'nonprofit', label: 'Nonprofit' },
    ],
    defaultValue: 'individual',
  }),

  // Business-only fields
  FormFieldClass.text('companyName', {
    label: 'Company Name',
    showWhen: (v) => v.role === 'business' || v.role === 'nonprofit',
    requiredWhen: (v) => v.role === 'business' || v.role === 'nonprofit',
  }),
  FormFieldClass.text('taxId', {
    label: 'Tax ID / EIN',
    showWhen: (v) => v.role === 'business',
    requiredWhen: (v) => v.role === 'business',
  }),
  FormFieldClass.text('nonprofitId', {
    label: '501(c)(3) Number',
    showWhen: (v) => v.role === 'nonprofit',
  }),
  FormFieldClass.number('employees', {
    label: 'Number of Employees',
    showWhen: (v) => v.role === 'business',
    min: 1,
  }),
]

Shipping form with address toggle

const shippingFields = [
  FormFieldClass.checkbox('sameAsBilling', {
    label: 'Shipping address same as billing',
    defaultValue: true,
  }),

  FormFieldClass.text('shippingStreet', {
    label: 'Street Address',
    showWhen: (v) => !v.sameAsBilling,
    requiredWhen: (v) => !v.sameAsBilling,
  }),
  FormFieldClass.text('shippingCity', {
    label: 'City',
    showWhen: (v) => !v.sameAsBilling,
    requiredWhen: (v) => !v.sameAsBilling,
    wrapperClassName: 'col-span-1',
  }),
  FormFieldClass.text('shippingState', {
    label: 'State',
    showWhen: (v) => !v.sameAsBilling,
    requiredWhen: (v) => !v.sameAsBilling,
    wrapperClassName: 'col-span-1',
  }),
  FormFieldClass.text('shippingZip', {
    label: 'ZIP Code',
    showWhen: (v) => !v.sameAsBilling,
    requiredWhen: (v) => !v.sameAsBilling,
    wrapperClassName: 'col-span-1',
  }),
]

Progressive disclosure

const surveyFields = [
  FormFieldClass.radio('satisfaction', {
    label: 'How satisfied are you?',
    radioOptions: [
      { value: '5', label: 'Very satisfied' },
      { value: '4', label: 'Satisfied' },
      { value: '3', label: 'Neutral' },
      { value: '2', label: 'Dissatisfied' },
      { value: '1', label: 'Very dissatisfied' },
    ],
  }),
  FormFieldClass.textArea('feedback', {
    label: 'What could we improve?',
    showWhen: (v) => v.satisfaction && parseInt(v.satisfaction) <= 3,
    requiredWhen: (v) => v.satisfaction && parseInt(v.satisfaction) <= 2,
    placeholder: 'Please tell us how we can do better...',
    rows: 4,
  }),
  FormFieldClass.checkbox('contactMe', {
    label: 'I would like to be contacted about my feedback',
    showWhen: (v) => v.satisfaction && parseInt(v.satisfaction) <= 3,
  }),
  FormFieldClass.phone('callbackNumber', {
    label: 'Callback Number',
    showWhen: (v) => v.contactMe === true,
    requiredWhen: (v) => v.contactMe === true,
  }),
]
Previous
Validation