Dynamic Validation with Ant Design Forms: From Checkboxes to Email Lists
Learn how Ant Design’s Form component lets you declare dynamic validation rules with dependencies and Form.List for complex, user‑friendly forms. A step‑by‑step example shows checkboxes, email lists, instant feedback, and best‑practice trade‑offs.
28 Sept 2026, 15:02 UTC

Concrete Problem: Conditional Fields in Real‑World Forms
When building a signup or survey form you often need a field that only makes sense once the user has checked a box, or a list of email addresses that can grow or shrink on the fly. Without a clean way to tie validation rules to other fields, developers end up writing custom state handlers, duplicating logic, and risking inconsistent UX.
Thesis: Ant Design’s Form Lets You Declare Dynamic Rules Declaratively
Ant Design (v4+) introduces two powerful props that turn validation from a procedural chore into a declarative declaration:
dependencies– makes aForm.Itemre‑evaluate its rules whenever specified sibling fields change.Form.List– manages dynamic arrays of fields, automatically handling key generation and validation for each item.
When combined with onValuesChange for instant feedback, you can build complex, user‑friendly forms with minimal boilerplate.
Section 1 – Declarative Rules & Dependencies
Each Form.Item can receive a rules array. Rules can be static objects or functions that receive the form context. The dependencies prop tells Ant Design to re‑run these rules whenever the referenced fields change.
const [form] = Form.useForm();
Agree to terms
Key points:
dependenciesforces a re‑validation whenagreechanges.- The rule function can access the current form state via
getFieldValue. - Using
onValuesChangewithvalidateFieldsgives instant visual feedback without submitting.
Section 2 – Dynamic Field Arrays with Form.List
When users need to add or remove items (e.g., multiple email addresses), Form.List handles the array logic and ensures each field gets a unique key.
{(fields, { add, remove }) => (
<>
{fields.map(({ key, name, fieldKey, ...restField }) => (
<Space key={key} style={{ display: 'flex', marginBottom: 8 }} align="baseline">
<Form.Item
{...restField}
name={name}
fieldKey={fieldKey}
rules={[{ type: 'email', required: true, message: 'Valid email required' }]}>
<Input placeholder="Email" />
</Form.Item>
<MinusCircleOutlined onClick={() => remove(name)} />
</Space>
))}
<Form.Item>
<Button type="dashed" onClick={() => add()} block>
<PlusOutlined /> Add Email
</Button>
</Form.Item>
<>
);
}
Best practices:
- Let
Form.Listgenerate keys; avoid hard‑codingkeyvalues that could duplicate. - Apply field‑level rules inside the mapping function so each email is validated independently.
- Wrap the list in a
SpaceorRowfor clean layout.
Section 3 – Instant Feedback with onValuesChange
For large forms, running validateFields on every keystroke can hurt performance. Use onValuesChange sparingly and target only the fields that need re‑validation.
onValuesChange={(_, all) => {
// Validate only the dependent field when its trigger changes
form.validateFields(['dependent']);
}}
When you need asynchronous validation (e.g., checking username availability), return a Promise in the rule:
rules={[
{
validator: (_, value) =>
api.checkUsername(value).then(isTaken => {
return isTaken
? Promise.reject(new Error('Username already taken'))
: Promise.resolve();
}),
},
]}
Worked Example – A Full Form
Below is a minimal, self‑contained example that ties everything together. Replace the api.checkUsername placeholder with your own async call.
import React from 'react';
import { Form, Input, Button, Checkbox, Space } from 'antd';
import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons';
const DemoForm = () => {
const [form] = Form.useForm();
const onFinish = values => {
console.log('Submitted:', values);
};
return (
<Form form={form} onFinish={onFinish} onValuesChange={(_, all) => form.validateFields(['dependent'])} layout="vertical">
Agree to terms
{(fields, { add, remove }) => (
<>
{fields.map(({ key, name, fieldKey, ...restField }) => (
<Space key={key} style={{ display: 'flex', marginBottom: 8 }} align="baseline">
<Form.Item
{...restField}
name={name}
fieldKey={fieldKey}
rules={[{ type: 'email', required: true, message: 'Valid email required' }]}
>
<Input placeholder="Email" />
</Form.Item>
<MinusCircleOutlined onClick={() => remove(name)} />
</Space>
))}
<Form.Item>
<Button type="dashed" onClick={() => add()} block>
<PlusOutlined /> Add Email
</Button>
</Form.Item>
<>
)}
<Form.Item>
<Button type="primary" htmlType="submit">Submit</Button>
</Form.Item>
</Form>
);
};
export default DemoForm;
Trade‑Offs & Limitations
- Performance:
onValuesChangetriggers validation on every change. For very large forms, consider debouncing or validating only on blur. - Key Management:
Form.Listrelies on unique keys. Using non‑unique keys can cause React reconciliation issues and unpredictable validation. - Async Rules: Must return a
Promise. Returning a plain value triggers a warning and can break the form’s state. - Version Compatibility:
dependenciesandForm.Listare available only from Ant Design v4.0 onward. Older projects need manual state handling. - Locale Handling: Ant Design does not auto‑switch locale; you must provide a
localeprop or context if your app supports multiple languages.
Actionable Take‑Away
To adopt dynamic validation in your next Ant Design form:
- Upgrade to v4+ if you haven’t already.
- Use
dependenciesfor fields that depend on others. - Wrap repeating groups in
Form.Listand keep each item’s key unique. - Validate on change only for the fields that actually need it; otherwise validate on submit.
- Test edge cases: check that the dependent field is ignored when its trigger is off, and that removing an item clears its validation errors.
With these patterns, you can deliver forms that feel responsive, reduce boilerplate, and keep validation logic close to the UI definition.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.