#Modal/DrawerForm - Floating Forms
ModalForm and DrawerForm are a variant of ProForm, which is still a form in essence. So you can't customize the footer with footer, if you want to define the footer you need to use submitter.render to customize it. These two forms behave like ProForm and can be modified directly from ProForm.
ModalForm and DrawerForm both provide triggers to reduce state usage, if you need to use state control you can use open and onOpenChange to control opening and closing.
#Modal Forms
import { PlusOutlined } from '@ant-design/icons';
import {
ModalForm,
ProForm,
ProFormDateRangePicker,
ProFormSelect,
ProFormText,
} from '@ant-design/pro-components';
import { Button, Form, message } from 'antd';
const waitTime = (time: number = 100) => {
return new Promise((resolve) => {
setTimeout(() => {
resolve(true);
}, time);
});
};
const Demo = () => {
const [form] = Form.useForm<{ name: string; company: string }>();
return (
<ModalForm<{
name: string;
company: string;
}>
name="modal-form-demo"
title="Create New Form"
trigger={
<Button type="primary">
<PlusOutlined />
Create New Form
</Button>
}
form={form}
autoFocusFirstInput
modalProps={{
destroyOnHidden: true,
onCancel: () => {
message.info('已关闭');
},
}}
submitTimeout={2000}
onFinish={async () => {
await waitTime(2000);
message.success('Submission successful');
return true;
}}
>
<ProForm.Group>
<ProFormText
width="md"
name="name"
label="Contract Customer Name"
tooltip="Up to 24 characters"
placeholder="Please enter a name"
/>
<ProFormText
width="md"
name="company"
label="Our Company Name"
placeholder="Please enter a name"
/>
</ProForm.Group>
<ProForm.Group>
<ProFormText
width="md"
name="contract"
label="Contract Name"
placeholder="Please enter a name"
/>
<ProFormDateRangePicker
name="contractTime"
label="Contract Effective Time"
/>
</ProForm.Group>
<ProForm.Group>
<ProFormSelect
request={async () => [
{
value: 'chapter',
label: 'Effective after stamping',
},
]}
width="xs"
name="useMode"
label="Contract Agreed Effective Method"
/>
<ProFormSelect
width="xs"
options={[
{
value: 'time',
label: 'Terminate after performance',
},
]}
name="unusedMode"
label="Contract Agreed Invalid Method"
/>
</ProForm.Group>
<ProFormText width="sm" name="id" label="Main Contract Number" />
<ProFormText
name="project"
disabled
label="Project Name"
initialValue="示例项目"
/>
<ProFormText
width="xs"
name="mangerName"
disabled
label="Business Manager"
initialValue="书琰"
/>
</ModalForm>
);
};
export default () => (
<div style={{ padding: 24 }}>
<Demo />
</div>
);
#Drawer Forms
import { PlusOutlined } from '@ant-design/icons';
import {
DrawerForm,
ProForm,
ProFormDateRangePicker,
ProFormSelect,
ProFormText,
} from '@ant-design/pro-components';
import { Button, Form, message } from 'antd';
const waitTime = (time: number = 100) => {
return new Promise((resolve) => {
setTimeout(() => {
resolve(true);
}, time);
});
};
const Demo = () => {
const [form] = Form.useForm<{ name: string; company: string }>();
return (
<DrawerForm<{
name: string;
company: string;
}>
name="modal-form-drawer-form-demo"
title="Create New Form"
resize={{
maxWidth: typeof window === 'undefined' ? 800 : window.innerWidth * 0.8,
minWidth: 300,
}}
form={form}
trigger={
<Button type="primary">
<PlusOutlined />
Create New Form
</Button>
}
autoFocusFirstInput
drawerProps={{
destroyOnHidden: true,
}}
submitTimeout={2000}
onFinish={async () => {
await waitTime(2000);
message.success('Submission successful');
// Not returning will not close the modal
return true;
}}
>
<ProForm.Group>
<ProFormText
name="name"
width="md"
label="Contract Customer Name"
tooltip="Up to 24 characters"
placeholder="Please enter a name"
/>
<ProFormText
rules={[
{
required: true,
},
]}
width="md"
name="company"
label="Our Company Name"
placeholder="Please enter a name"
/>
</ProForm.Group>
<ProForm.Group>
<ProFormText
width="md"
name="contract"
label="Contract Name"
placeholder="Please enter a name"
/>
<ProFormDateRangePicker
name="contractTime"
label="Contract Effective Time"
/>
</ProForm.Group>
<ProForm.Group>
<ProFormSelect
options={[
{
value: 'chapter',
label: 'Effective after stamping',
},
]}
width="xs"
name="useMode"
label="Contract Agreed Effective Method"
/>
<ProFormSelect
width="xs"
options={[
{
value: 'time',
label: 'Terminate after performance',
},
]}
formItemProps={{
style: {
margin: 0,
},
}}
name="unusedMode"
label="Contract Agreed Invalid Method"
/>
</ProForm.Group>
<ProFormText width="sm" name="id" label="Main Contract Number" />
<ProFormText
name="project"
disabled
label="Project Name"
initialValue="示例项目"
/>
<ProFormText
width="xs"
name="mangerName"
disabled
label="Business Manager"
initialValue="书琰"
/>
</DrawerForm>
);
};
export default () => (
<div style={{ padding: 24 }}>
<Demo />
</div>
);
#Nested Drawer Forms
import { PlusOutlined } from '@ant-design/icons';
import {
DrawerForm,
ModalForm,
ProForm,
ProFormDateRangePicker,
ProFormSelect,
ProFormText,
} from '@ant-design/pro-components';
import { Button, Space, message } from 'antd';
import { useRef } from 'react';
const waitTime = (time: number = 100) => {
return new Promise((resolve) => {
setTimeout(() => {
resolve(true);
}, time);
});
};
const Demo = () => {
const formRef = useRef();
return (
<div
style={{
height: '325px',
}}
>
<Space>
<DrawerForm<{
name: string;
company: string;
}>
name="modal-form-drawer-form-nested-demo-4"
title="新建表单"
formRef={formRef}
width={600}
trigger={
<Button type="primary">
<PlusOutlined />
新建 Drawer 表单
</Button>
}
drawerProps={{
forceRender: true,
destroyOnHidden: true,
}}
onFinish={async () => {
await waitTime(2000);
message.success('提交成功');
// 不返回不会关闭弹框
return true;
}}
>
<DrawerForm<{
name: string;
company: string;
}>
name="modal-form-drawer-form-nested-demo-3"
title="新建 表单"
formRef={formRef}
trigger={
<Button type="primary">
<PlusOutlined />
新建 Drawer 表单
</Button>
}
drawerProps={{
forceRender: true,
destroyOnHidden: true,
}}
onFinish={async () => {
await waitTime(2000);
message.success('提交成功');
// 不返回不会关闭弹框
return true;
}}
>
<ProForm.Group>
<ProFormText
name="name"
width="md"
label="签约客户名称"
tooltip="最长为 24 位"
placeholder="请输入名称"
/>
<ProFormText
width="md"
name="company"
label="我方公司名称"
placeholder="请输入名称"
/>
</ProForm.Group>
<ProForm.Group>
<ProFormText
width="md"
name="contract"
label="合同名称"
placeholder="请输入名称"
/>
<ProFormDateRangePicker
name="contractTime"
label="合同生效时间"
/>
</ProForm.Group>
<ProForm.Group>
<ProFormSelect
options={[
{
value: 'chapter',
label: '盖章后生效',
},
]}
width="xs"
name="useMode"
label="合同约定生效方式"
/>
<ProFormSelect
width="xs"
options={[
{
value: 'time',
label: '履行完终止',
},
]}
name="unusedMode"
label="合同约定失效效方式"
/>
</ProForm.Group>
<ProFormText width="sm" name="id" label="主合同编号" />
<ProFormText
name="project"
disabled
label="项目名称"
initialValue="示例项目"
/>
<ProFormText
width="xs"
name="mangerName"
disabled
label="商务经理"
initialValue="书琰"
/>
</DrawerForm>
</DrawerForm>
<ModalForm<{
name: string;
company: string;
}>
name="modal-form-drawer-form-nested-demo-2"
title="新建表单"
formRef={formRef}
trigger={
<Button type="primary">
<PlusOutlined />
新建 Model 表单
</Button>
}
modalProps={{
forceRender: true,
destroyOnHidden: true,
}}
onFinish={async () => {
await waitTime(2000);
message.success('提交成功');
// 不返回不会关闭弹框
return true;
}}
>
<ModalForm<{
name: string;
company: string;
}>
name="modal-form-drawer-form-nested-demo-1"
title="新建表单"
formRef={formRef}
trigger={
<Button type="primary">
<PlusOutlined />
新建 Model 表单
</Button>
}
modalProps={{
forceRender: true,
destroyOnHidden: true,
}}
onFinish={async () => {
await waitTime(2000);
message.success('提交成功');
// 不返回不会关闭弹框
return true;
}}
>
<ProForm.Group>
<ProFormText
name="name"
width="md"
label="签约客户名称"
tooltip="最长为 24 位"
placeholder="请输入名称"
/>
<ProFormText
width="md"
name="company"
label="我方公司名称"
placeholder="请输入名称"
/>
</ProForm.Group>
<ProForm.Group>
<ProFormText
width="md"
name="contract"
label="合同名称"
placeholder="请输入名称"
/>
<ProFormDateRangePicker
name="contractTime"
label="合同生效时间"
/>
</ProForm.Group>
<ProForm.Group>
<ProFormSelect
options={[
{
value: 'chapter',
label: '盖章后生效',
},
]}
width="xs"
name="useMode"
label="合同约定生效方式"
/>
<ProFormSelect
width="xs"
options={[
{
value: 'time',
label: '履行完终止',
},
]}
name="unusedMode"
label="合同约定失效效方式"
/>
</ProForm.Group>
<ProFormText width="sm" name="id" label="主合同编号" />
<ProFormText
name="project"
disabled
label="项目名称"
initialValue="示例项目"
/>
<ProFormText
width="xs"
name="mangerName"
disabled
label="商务经理"
initialValue="书琰"
/>
</ModalForm>
</ModalForm>
</Space>
</div>
);
};
export default () => (
<div style={{ padding: 24 }}>
<Demo />
</div>
);
#Debug: Close while loading (Modal + Drawer)
import { DrawerForm, ModalForm, ProFormText } from '@ant-design/pro-components';
import { Button, message } from 'antd';
const wait = (time: number = 300) =>
new Promise((resolve) => setTimeout(resolve, time));
const Demo = () => {
return (
<>
<ModalForm
title="Debug: destroyOnHidden + request"
trigger={<Button type="primary">Open (Debug)</Button>}
modalProps={{ destroyOnHidden: true }}
// Simulate async data loading
request={async () => {
await wait(3000);
return { name: 'loaded' } as any;
}}
onFinish={async () => {
message.success('Submission successful');
return true;
}}
>
<ProFormText
name="name"
label="Name"
placeholder="Close quickly while loading to test"
/>
</ModalForm>
<div style={{ height: 16 }} />
<DrawerForm
title="Debug: destroyOnHidden + request (Drawer)"
trigger={<Button type="primary">Open Drawer (Debug)</Button>}
drawerProps={{ destroyOnHidden: true }}
// Simulate async data loading
request={async () => {
await wait(3000);
return { name: 'loaded' } as any;
}}
onFinish={async () => {
message.success('Submission successful');
return true;
}}
>
<ProFormText
name="name"
label="Name"
placeholder="Close quickly while loading to test"
/>
</DrawerForm>
</>
);
};
export default () => (
<div style={{ padding: 24 }}>
<Demo />
</div>
);
#Custom Modal Forms' Button
import { ModalForm, ProFormText } from '@ant-design/pro-components';
import { Button, Space, message } from 'antd';
const waitTime = (time: number = 100) => {
return new Promise((resolve) => {
setTimeout(() => {
resolve(true);
}, time);
});
};
const Demo = () => {
return (
<Space>
<ModalForm
title="Create New Form"
trigger={<Button type="primary">Custom Footer Buttons</Button>}
submitter={{
render: (props, defaultDoms) => {
return [
...defaultDoms,
<Button
key="ok"
onClick={() => {
props.submit();
}}
>
ok
</Button>,
];
},
}}
onFinish={async () => {
await waitTime(2000);
message.success('Submission successful');
return true;
}}
>
<ProFormText
width="md"
name="name"
label="Contract Customer Name"
tooltip="Up to 24 characters"
placeholder="Please enter a name"
/>
<ProFormText
width="md"
name="company"
label="Our Company Name"
placeholder="Please enter a name"
/>
</ModalForm>
<ModalForm
title="Create New Form"
trigger={<Button type="primary">Custom Text</Button>}
submitter={{
searchConfig: {
submitText: 'Confirm',
resetText: 'Cancel',
},
}}
onFinish={async () => {
await waitTime(2000);
message.success('Submission successful');
return true;
}}
>
<ProFormText
width="md"
name="name"
label="Contract Customer Name"
tooltip="Up to 24 characters"
placeholder="Please enter a name"
/>
<ProFormText
width="md"
name="company"
label="Our Company Name"
placeholder="Please enter a name"
/>
</ModalForm>
<ModalForm
title="Create New Form"
trigger={<Button type="primary">Hide or Modify Button Style</Button>}
submitter={{
resetButtonProps: {
type: 'dashed',
},
submitButtonProps: {
style: {
display: 'none',
},
},
}}
onFinish={async () => {
await waitTime(2000);
message.success('Submission successful');
return true;
}}
>
<ProFormText
width="md"
name="name"
label="Contract Customer Name"
tooltip="Up to 24 characters"
placeholder="Please enter a name"
/>
<ProFormText
width="md"
name="company"
label="Our Company Name"
placeholder="Please enter a name"
/>
</ModalForm>
<ModalForm
title="Hide Footer"
trigger={<Button type="primary">Hide Footer</Button>}
submitter={false}
>
<ProFormText
width="md"
name="name"
label="Contract Customer Name"
tooltip="Up to 24 characters"
placeholder="Please enter a name"
/>
<ProFormText
width="md"
name="company"
label="Our Company Name"
placeholder="Please enter a name"
/>
</ModalForm>
</Space>
);
};
export default () => (
<div style={{ padding: 24 }}>
<Demo />
</div>
);
#Use open and onOpenChange
import { PlusOutlined } from '@ant-design/icons';
import {
DrawerForm,
ModalForm,
ProForm,
ProFormDateRangePicker,
ProFormSelect,
ProFormText,
} from '@ant-design/pro-components';
import { Button, Space, message } from 'antd';
import { useState } from 'react';
const Demo = () => {
const [modalVisit, setModalVisit] = useState(false);
const [drawerVisit, setDrawerVisit] = useState(false);
return (
<>
<Space>
<Button
type="primary"
onClick={() => {
setModalVisit(true);
}}
>
<PlusOutlined />
Show Modal
</Button>
<Button
type="primary"
onClick={() => {
setDrawerVisit(true);
}}
>
<PlusOutlined />
Show Drawer
</Button>
</Space>
<ModalForm
title="Create New Form"
open={modalVisit}
onFinish={async () => {
message.success('Submission successful');
return true;
}}
onOpenChange={setModalVisit}
>
<ProForm.Group>
<ProFormText
width="md"
name="name"
label="Contract Customer Name"
tooltip="Up to 24 characters"
placeholder="Please enter a name"
/>
<ProFormText
width="md"
name="company"
label="Our Company Name"
placeholder="Please enter a name"
/>
</ProForm.Group>
<ProForm.Group>
<ProFormText
width="md"
name="contract"
label="Contract Name"
placeholder="Please enter a name"
/>
<ProFormDateRangePicker
name="contractTime"
label="Contract Effective Time"
/>
</ProForm.Group>
<ProForm.Group>
<ProFormSelect
options={[
{
value: 'chapter',
label: 'Effective after stamping',
},
]}
width="xs"
name="useMode"
label="Contract Agreed Effective Method"
/>
<ProFormSelect
width="xs"
options={[
{
value: 'time',
label: 'Terminate after performance',
},
]}
name="unusedMode"
label="Contract Agreed Invalid Method"
/>
</ProForm.Group>
<ProFormText width="sm" name="id" label="Main Contract Number" />
<ProFormText
name="project"
disabled
label="Project Name"
initialValue="示例项目"
/>
<ProFormText
width="xs"
name="mangerName"
disabled
label="Business Manager"
initialValue="书琰"
/>
</ModalForm>
<DrawerForm
onOpenChange={setDrawerVisit}
title="Create New Form"
open={drawerVisit}
onFinish={async () => {
message.success('Submission successful');
return true;
}}
>
<ProForm.Group>
<ProFormText
width="md"
name="name"
label="Contract Customer Name"
tooltip="Up to 24 characters"
placeholder="Please enter a name"
/>
<ProFormText
width="md"
name="company"
label="Our Company Name"
placeholder="Please enter a name"
/>
</ProForm.Group>
<ProForm.Group>
<ProFormText
width="md"
name="contract"
label="Contract Name"
placeholder="Please enter a name"
/>
<ProFormDateRangePicker
name="contractTime"
label="Contract Effective Time"
/>
</ProForm.Group>
<ProForm.Group>
<ProFormSelect
options={[
{
value: 'chapter',
label: 'Effective after stamping',
},
]}
width="xs"
name="useMode"
label="Contract Agreed Effective Method"
/>
<ProFormSelect
width="xs"
options={[
{
value: 'time',
label: 'Terminate after performance',
},
]}
name="unusedMode"
label="Contract Agreed Invalid Method"
/>
</ProForm.Group>
<ProFormText width="sm" name="id" label="Main Contract Number" />
<ProFormText
name="project"
disabled
label="Project Name"
initialValue="示例项目"
/>
<ProFormText
width="xs"
name="mangerName"
disabled
label="Business Manager"
initialValue="书琰"
/>
</DrawerForm>
</>
);
};
export default () => (
<div style={{ padding: 24 }}>
<Demo />
</div>
);
#Set Form Values Before the First Open
antd Modal renders its content lazily by default. Before the first open, an
instance created by Form.useForm() is not connected to the Form inside
ModalForm, so calling setFieldsValue produces the “Instance created by
useForm is not connected” warning. Passing form={form} alone does not mount
the Form early.
If the interaction must call setFieldsValue before opening the modal, set
modalProps={{ forceRender: true }}. The following example is directly
verifiable:
import { ModalForm, ProForm, ProFormText } from '@ant-design/pro-components';
import { Button, Space, message } from 'antd';
import { useState } from 'react';
type User = {
name: string;
email: string;
};
const users: User[] = [
{ name: 'Alice', email: 'alice@example.com' },
{ name: 'Bob', email: 'bob@example.com' },
];
export default () => {
const [form] = ProForm.useForm<User>();
const [open, setOpen] = useState(false);
const handleEdit = (user: User) => {
// forceRender connects this instance before the first button click.
form.setFieldsValue(user);
setOpen(true);
};
return (
<>
<Space>
{users.map((user) => (
<Button key={user.email} onClick={() => handleEdit(user)}>
Edit {user.name}
</Button>
))}
</Space>
<ModalForm<User>
form={form}
open={open}
onOpenChange={setOpen}
modalProps={{ forceRender: true }}
title="Edit user"
onFinish={async (values) => {
message.success(`Saved ${values.name}`);
return true;
}}
>
<ProFormText name="name" label="Name" rules={[{ required: true }]} />
<ProFormText name="email" label="Email" />
</ModalForm>
</>
);
};
You can also avoid calling the instance too early:
- Use
initialValuesorrequestso data is initialized after the Form mounts. When editing different records, combine this withmodalProps={{ destroyOnHidden: true }}so the next open mounts a fresh Form. - Capture or use the connected instance in
onInit. - When using ModalForm's built-in
trigger, write values inonOpenChange(true). In v3, the first open callback is flushed after the internal Form mounts.
#Reset Form
import type { ProFormInstance } from '@ant-design/pro-components';
import { ModalForm, ProFormText } from '@ant-design/pro-components';
import { Button, Space, message } from 'antd';
import { useRef, useState } from 'react';
const waitTime = (time: number = 100) => {
return new Promise((resolve) => {
setTimeout(() => {
resolve(true);
}, time);
});
};
const Demo = () => {
const restFormRef = useRef<ProFormInstance>();
const formRef = useRef<ProFormInstance>();
const [modalVisible, setModalVisible] = useState<boolean>(false);
return (
<Space>
<ModalForm
title="Create New Form"
formRef={restFormRef}
open={modalVisible}
trigger={
<Button
type="primary"
onClick={() => {
setModalVisible(true);
}}
>
Reset via formRef
</Button>
}
onOpenChange={setModalVisible}
submitter={{
searchConfig: {
resetText: 'Reset',
},
resetButtonProps: {
onClick: () => {
restFormRef.current?.resetFields();
// setModalVisible(false);
},
},
}}
onFinish={async () => {
await waitTime(2000);
message.success('Submission successful');
return true;
}}
>
<ProFormText
width="md"
name="name"
label="Contract Customer Name"
tooltip="Up to 24 characters"
placeholder="Please enter a name"
/>
<ProFormText
width="md"
name="company"
label="Our Company Name"
placeholder="Please enter a name"
/>
</ModalForm>
<ModalForm
title="Create New Form"
formRef={formRef}
trigger={<Button type="primary">Reset via custom footer button</Button>}
submitter={{
render: (props, defaultDoms) => {
return [
...defaultDoms,
<Button
key="extra-reset"
onClick={() => {
props.reset();
}}
>
Reset
</Button>,
];
},
}}
onFinish={async () => {
await waitTime(2000);
message.success('Submission successful');
return true;
}}
>
<ProFormText
width="md"
name="name"
label="Contract Customer Name"
tooltip="Up to 24 characters"
placeholder="Please enter a name"
/>
<ProFormText
width="md"
name="company"
label="Our Company Name"
placeholder="Please enter a name"
/>
</ModalForm>
</Space>
);
};
export default () => (
<div style={{ padding: 24 }}>
<Demo />
</div>
);
#API
#ModalForm
ModalForm combines Modal and ProForm to reduce tedious state management.
| Parameters | Description | Type | Default |
|---|---|---|---|
| trigger | The dom used to trigger the Modal to open, usually the button | JSX.Element | - |
| open | whether to open | boolean | - |
| onOpenChange | trigger when open changes | (open:boolean)=>void | - |
| modalProps | Modal's props, used in the same way as antd. Note: 'open' is not supported, please use the global open | Omit<ModalProps, 'open'> | - |
| title | The title of the popup box | ModalProps['title'] | - |
| width | The width of the modal | ModalProps['width'] | - |
| onFinish | Triggered when submitting data. If returns a truthy value, it will close the modal (and reset form if destroyOnHidden is enabled) | (values: any) => Promise<any> | - |
| submitTimeout | Disable timeout for the Cancel button when submitting data (ms) | number | - |
| submitter | Submit button configurations in the same way as ProForm | ProForm | - |
#DrawerForm
DrawerForm combines Drawer and ProForm to reduce tedious state management.
| Parameters | Description | Type | Default |
|---|---|---|---|
| trigger | The dom used to trigger the opening of the Modal, typically the button | JSX.Element | - |
| open | whether to open | boolean | - |
| onOpenChange | trigger when open changes | (open:boolean)=>void | - |
| drawerProps | Drawer's props, used in the same way as antd. Note: 'open' is not supported, please use the global open | Omit<DrawerProps, 'open'> | - |
| title | The title of the drawer | DrawerProps['title'] | - |
| size | Size of the drawer | DrawerProps['size'] | - |
| width | Width of the drawer, deprecated, please use size | DrawerProps['size'] | - |
| resizable | Whether to use antd native drag resize (ignored when resize is set) | DrawerProps['resizable'] | - |
| resize | Whether to allow drag resize. When set to true, default config will be used | CustomizeResizeType | boolean | false |
| onFinish | Triggered when submitting data. If returns a truthy value, it will close the drawer (and reset form if destroyOnHidden is enabled) | (values: any) => Promise<any> | - |
| submitTimeout | Disable timeout for the Cancel button when submitting data (ms) | number | - |