Advanced field components provide functionality for dynamic lists, field dependencies, and grouped fields.
Dynamic list component for managing multiple items with add/remove/sort operations.
Import
import { ProFormList } from '@ant-design/pro-components';
Usage
Basic
With Actions
Nested Structure
Custom Render
<ProFormList
name="contacts"
label="Contacts"
creatorButtonProps={{
creatorButtonText: 'Add Contact'
}}
>
<ProFormText name="name" label="Name" />
<ProFormText name="phone" label="Phone" />
<ProFormText name="email" label="Email" />
</ProFormList>
<ProFormList
name="tasks"
label="Tasks"
actionRender={(field, action, defaultActionDom) => [
defaultActionDom,
<Button
key="custom"
onClick={() => {
console.log('Current item:', field);
}}
>
Custom Action
</Button>,
]}
creatorButtonProps={{
creatorButtonText: 'Add Task',
position: 'bottom'
}}
>
<ProFormText name="title" label="Title" />
<ProFormTextArea name="description" label="Description" />
<ProFormDatePicker name="dueDate" label="Due Date" />
</ProFormList>
<ProFormList
name="orders"
label="Orders"
>
<ProFormText name="orderNumber" label="Order #" />
<ProFormList
name="items"
label="Items"
creatorButtonProps={{
creatorButtonText: 'Add Item'
}}
>
<ProFormText name="product" label="Product" />
<ProFormDigit name="quantity" label="Quantity" />
<ProFormDigit name="price" label="Price" />
</ProFormList>
</ProFormList>
<ProFormList
name="members"
label="Team Members"
itemRender={({ listDom, action }, { index }) => (
<Card
title={`Member ${index + 1}`}
extra={action}
style={{ marginBottom: 16 }}
>
{listDom}
</Card>
)}
>
{(meta, index) => [
<ProFormText
key="name"
name="name"
label="Name"
placeholder={`Member ${index + 1} name`}
/>,
<ProFormSelect
key="role"
name="role"
label="Role"
options={[
{ label: 'Admin', value: 'admin' },
{ label: 'User', value: 'user' },
]}
/>,
]}
</ProFormList>
Props
name
string | string[]
required
Field name for the list in form values.
Label displayed above the list.
children
ReactNode | ((meta: FormListFieldData, index: number) => ReactNode)
List item content. Can be:
- Static JSX elements
- Function receiving meta data and index for dynamic rendering
actionRef
MutableRefObject<FormListActionType>
Ref to access list operations imperatively:interface FormListActionType {
add: (defaultValue?: any, insertIndex?: number) => void;
remove: (index: number) => void;
move: (from: number, to: number) => void;
get: (index: number) => any;
getList: () => any[];
}
Usage:const actionRef = useRef<FormListActionType>();
<ProFormList actionRef={actionRef} name="items" />
// Later
actionRef.current?.add({ name: 'New Item' });
actionRef.current?.remove(0);
actionRef.current?.move(0, 2);
creatorButtonProps
false | ButtonProps & { creatorButtonText?: string, position?: 'top' | 'bottom' }
Props for the “Add” button. Set to false to hide button:creatorButtonProps={{
creatorButtonText: 'Add Item',
position: 'bottom',
type: 'dashed',
block: true,
icon: <PlusOutlined />
}}
creatorRecord
Record<string, any> | (() => Record<string, any>)
Default values for new items:creatorRecord={{ status: 'active', quantity: 1 }}
// or
creatorRecord={() => ({ id: generateId(), date: new Date() })}
actionRender
(field: FormListFieldData, action: FormListOperation, defaultActionDom: ReactNode[], count: number) => ReactNode[]
Custom render function for item actions (copy, delete, move buttons):actionRender={(field, action, defaultActionDom) => [
...defaultActionDom,
<Button onClick={() => console.log(field)}>View</Button>
]}
itemRender
(doms: { listDom: ReactNode, action: ReactNode }, listMeta: { field: FormListFieldData, fields: FormListFieldData[], index: number, operation: FormListOperation, record: any, meta: FormListMeta }) => ReactNode
Custom render function for entire list item including actions:itemRender={({ listDom, action }, { index, record }) => (
<div className="custom-item">
<div className="item-header">
Item {index + 1}
<div>{action}</div>
</div>
<div className="item-body">{listDom}</div>
</div>
)}
itemContainerRender
(doms: ReactNode) => ReactNode
Wrapper for all list items:itemContainerRender={(doms) => <Space direction="vertical">{doms}</Space>}
Minimum number of items. Delete button is disabled when limit is reached.
Maximum number of items. Add button is disabled when limit is reached.
copyIconProps
{ Icon?: ReactNode, tooltipText?: string } | false
Props for copy button. Set to false to hide:copyIconProps={{ Icon: <CopyOutlined />, tooltipText: 'Duplicate' }}
deleteIconProps
{ Icon?: ReactNode, tooltipText?: string } | false
Props for delete button. Set to false to hide:deleteIconProps={{ Icon: <DeleteOutlined />, tooltipText: 'Remove' }}
Whether to show up/down arrow buttons for sorting items.
upIconProps
{ Icon?: ReactNode, tooltipText?: string }
Props for move up button (when arrowSort is enabled).
downIconProps
{ Icon?: ReactNode, tooltipText?: string }
Props for move down button (when arrowSort is enabled).
Whether to always show field labels in list items. When false, labels are hidden after the first item.
actionGuard
{ beforeAddRow?: (index: number) => boolean | Promise<boolean>, beforeRemoveRow?: (index: number) => boolean | Promise<boolean> }
Guard functions called before add/remove operations:actionGuard={{
beforeAddRow: async (index) => {
const confirmed = await confirm('Add new item?');
return confirmed;
},
beforeRemoveRow: async (index) => {
const confirmed = await confirm('Delete this item?');
return confirmed;
}
}}
onAfterAdd
(defaultValue: any, insertIndex: number, count: number) => void
Callback after item is added successfully.
onAfterRemove
(index: number, count: number) => void
Callback after item is removed successfully.
Whether to validate that the list is not empty.
emptyListMessage
string
default:"'List cannot be empty'"
Validation message when list is empty (when isValidateList is true).
Validation rules for the list. Each rule can include required property.
Whether the list is required.
Whether the list is read-only. In read-only mode, action buttons are hidden.
transform
(value: any[], field: NamePath) => any
Transform list data when submitting form.
Grid column props for the list container.
Grid row props for list items.
Component for creating field dependencies and conditional rendering based on form values.
Import
import { ProFormDependency } from '@ant-design/pro-components';
Usage
Basic
Multiple Fields
Nested Path
With Form Instance
<ProFormSelect
name="type"
label="Type"
options={[
{ label: 'Person', value: 'person' },
{ label: 'Company', value: 'company' },
]}
/>
<ProFormDependency name={['type']}>
{({ type }) => {
if (type === 'person') {
return (
<ProFormText
name="firstName"
label="First Name"
/>
);
}
if (type === 'company') {
return (
<ProFormText
name="companyName"
label="Company Name"
/>
);
}
return null;
}}
</ProFormDependency>
<ProFormSelect name="country" label="Country" />
<ProFormSelect name="state" label="State" />
<ProFormText name="city" label="City" />
<ProFormDependency name={['country', 'state', 'city']}>
{({ country, state, city }) => (
<Alert
message={`Location: ${city || ''}, ${state || ''}, ${country || ''}`}
type="info"
/>
)}
</ProFormDependency>
<ProFormDependency name={[['user', 'role'], ['user', 'permissions']]}>
{({ user }) => {
const { role, permissions } = user || {};
if (role === 'admin') {
return <Alert message="Admin has all permissions" />;
}
return (
<ProFormSelect
name={['user', 'permissions']}
label="Permissions"
mode="multiple"
options={availablePermissions}
/>
);
}}
</ProFormDependency>
<ProFormDependency name={['items']}>
{({ items }, form) => {
const total = items?.reduce((sum, item) =>
sum + (item.quantity || 0) * (item.price || 0), 0
);
return (
<ProFormMoney
name="total"
label="Total"
readonly
fieldProps={{
value: total
}}
/>
);
}}
</ProFormDependency>
Props
Array of field names to depend on. Each name can be:
- String:
['username']
- Array (nested path):
[['user', 'email']]
- Mixed:
['type', ['user', 'role']]
children
(values: Record<string, any>, form: ProFormInstance) => ReactNode
required
Render function receiving dependent field values and form instance:{(values, form) => {
// values: object with field values
// form: ProForm instance with methods
return <div>{values.fieldName}</div>;
}}
When true, ignores ProFormList context and reads from global form values instead of list item values.
shouldUpdate
boolean | ((prevValues: any, nextValues: any, info: { source?: string }) => boolean)
Custom condition for when to re-render. By default, re-renders when dependent field values change:shouldUpdate={(prev, next) => {
return prev.username !== next.username;
}}
Component for grouping multiple fields into a single form value.
Import
import { ProFormFieldSet } from '@ant-design/pro-components';
Usage
Basic
Mixed Inputs
Compact Group
With ProForm Fields
<ProFormFieldSet
name="dateRange"
label="Date Range"
>
<DatePicker placeholder="Start date" />
<DatePicker placeholder="End date" />
</ProFormFieldSet>
// Form value: { dateRange: [startDate, endDate] }
<ProFormFieldSet
name="price"
label="Price"
space={8}
>
<InputNumber placeholder="Min" style={{ width: 120 }} />
<span>-</span>
<InputNumber placeholder="Max" style={{ width: 120 }} />
</ProFormFieldSet>
// Form value: { price: [minPrice, maxPrice] }
<ProFormFieldSet
name="address"
label="Address"
type="group"
>
<Input placeholder="Street" />
<Input placeholder="City" />
<Input placeholder="Zip" style={{ width: 100 }} />
</ProFormFieldSet>
// Form value: { address: [street, city, zip] }
<ProFormFieldSet
name="contact"
label="Contact Info"
>
<ProFormText
name="email"
placeholder="Email"
fieldProps={{ style: { width: 200 } }}
/>
<ProFormText
name="phone"
placeholder="Phone"
fieldProps={{ style: { width: 150 } }}
/>
</ProFormFieldSet>
// Form value: { contact: [email, phone] }
Props
name
string | string[]
required
Field name in form values. The value will be an array of child field values.
Label displayed above the field set.
type
'space' | 'group'
default:"'space'"
Layout type:
space: Uses Ant Design Space component (with gaps)
group: Uses Space.Compact (no gaps, connected borders)
Props for the Space or Space.Compact component:// For type="space"
space={{ size: 16, direction: 'vertical', align: 'start' }}
// For type="group"
space={{ block: true }}
children
ReactNode | ((value: any[], props: ProFormFieldSetProps) => ReactNode)
Child fields. Can be static or function for dynamic rendering:{(value, props) => (
<>
<Input value={value[0]} />
<Input value={value[1]} />
</>
)}
Controlled value. Array of values for each child field.
Callback when any child field value changes.
transform
(value: any[], field: NamePath) => any
Transform fieldset value when submitting form:transform={(value) => ({
min: value[0],
max: value[1]
})}
convertValue
(value: any, field: NamePath) => any[]
Convert value from form to fieldset format:convertValue={(value) => [
value?.min,
value?.max
]}
Grid column props for the fieldset container.
Grid row props for the fieldset.
Common Patterns
Show Dynamic Form with ProFormList
Create forms with variable number of items:const [form] = Form.useForm();
const actionRef = useRef<FormListActionType>();
<ProForm form={form}>
<ProFormList
name="items"
label="Items"
actionRef={actionRef}
min={1}
max={10}
creatorButtonProps={{
creatorButtonText: 'Add Item',
}}
actionGuard={{
beforeRemoveRow: async (index) => {
const values = actionRef.current?.get(index);
if (values?.isImportant) {
return confirm('This is important. Delete anyway?');
}
return true;
}
}}
>
<ProFormText name="name" label="Name" />
<ProFormDigit name="quantity" label="Quantity" />
<ProFormDependency name={['quantity']}>
{({ quantity }) => (
quantity > 100 ? (
<Alert message="Large quantity!" type="warning" />
) : null
)}
</ProFormDependency>
</ProFormList>
</ProForm>
Show Conditional Fields with ProFormDependency
Show/hide fields based on other field values:<ProFormRadio.Group
name="shippingMethod"
label="Shipping Method"
options={[
{ label: 'Standard', value: 'standard' },
{ label: 'Express', value: 'express' },
{ label: 'Pickup', value: 'pickup' },
]}
/>
<ProFormDependency name={['shippingMethod']}>
{({ shippingMethod }) => {
if (shippingMethod === 'pickup') {
return (
<>
<ProFormSelect
name="pickupLocation"
label="Pickup Location"
options={locations}
/>
<ProFormDatePicker
name="pickupDate"
label="Pickup Date"
/>
</>
);
}
if (shippingMethod) {
return (
<>
<ProFormText name="address" label="Shipping Address" />
<ProFormText name="city" label="City" />
<ProFormText name="zipCode" label="Zip Code" />
</>
);
}
return null;
}}
</ProFormDependency>
Show Grouped Fields with ProFormFieldSet
Combine related fields into array values:<ProForm
onFinish={async (values) => {
// values.priceRange = [minPrice, maxPrice]
// values.sizeRange = [width, height]
console.log(values);
}}
>
<ProFormFieldSet
name="priceRange"
label="Price Range"
transform={(value) => ({
minPrice: value[0],
maxPrice: value[1]
})}
>
<InputNumber
placeholder="Min"
prefix="$"
style={{ width: 150 }}
/>
<InputNumber
placeholder="Max"
prefix="$"
style={{ width: 150 }}
/>
</ProFormFieldSet>
<ProFormFieldSet
name="sizeRange"
label="Dimensions"
type="group"
>
<InputNumber placeholder="Width" addonAfter="cm" />
<InputNumber placeholder="Height" addonAfter="cm" />
</ProFormFieldSet>
</ProForm>
Create nested dynamic structures:<ProFormList name="categories" label="Categories">
<ProFormText name="name" label="Category Name" />
<ProFormList
name="products"
label="Products"
creatorButtonProps={{
creatorButtonText: 'Add Product'
}}
>
<ProFormText name="productName" label="Product" />
<ProFormDigit name="price" label="Price" />
<ProFormDigit name="stock" label="Stock" />
</ProFormList>
</ProFormList>
Form value structure:{
categories: [
{
name: 'Electronics',
products: [
{ productName: 'Laptop', price: 999, stock: 10 },
{ productName: 'Phone', price: 599, stock: 25 }
]
}
]
}