모달
모달 (Modal)
새 페이지로 이동하지 않고 현재 페이지 위에 떠 있는 레이어를 만들어 사용자 피드백을 받거나 정보를 보여 주는 컴포넌트예요. 사용자와 상호작용이 필요하지만 작업 흐름을 끊고 싶지 않을 때 써요.
출처: 문서
본문
언제 사용하나요 (When To Use)
사용자와의 상호작용이 필요하지만, 새 페이지로 이동해 사용자의 작업 흐름을 끊고 싶지 않을 때 Modal로 현재 페이지 위에 떠 있는 새 레이어를 만들어 피드백을 받거나 정보를 표시할 수 있어요.
또한, 간단한 확인 대화상자를 보여 주려면 App.useApp hooks를 사용할 수 있어요.
예시 (Examples)
기본 (Basic)
기본 모달이에요.
import React, { useState } from 'react';
import { Button, Modal } from 'antd';
const App: React.FC = () => {
const [isModalOpen, setIsModalOpen] = useState(false);
const showModal = () => {
setIsModalOpen(true);
};
const handleOk = () => {
setIsModalOpen(false);
};
const handleCancel = () => {
setIsModalOpen(false);
};
return (
<>
<Button type="primary" onClick={showModal}>
Open Modal
</Button>
<Modal
title="Basic Modal"
closable={{ 'aria-label': 'Custom Close Button' }}
open={isModalOpen}
onOk={handleOk}
onCancel={handleCancel}
>
<p>Some contents...</p>
<p>Some contents...</p>
<p>Some contents...</p>
</Modal>
</>
);
};
export default App;
비동기 닫기 (Asynchronously close)
OK 버튼을 눌렀을 때 모달 대화상자를 비동기로 닫아요. 예를 들어 폼을 제출할 때 이 패턴을 사용할 수 있어요.
import React, { useState } from 'react';
import { Button, Modal } from 'antd';
const App: React.FC = () => {
const [open, setOpen] = useState(false);
const [confirmLoading, setConfirmLoading] = useState(false);
const [modalText, setModalText] = useState('Content of the modal');
const showModal = () => {
setOpen(true);
};
const handleOk = () => {
setModalText('The modal will be closed after two seconds');
setConfirmLoading(true);
setTimeout(() => {
setOpen(false);
setConfirmLoading(false);
}, 2000);
};
const handleCancel = () => {
console.log('Clicked cancel button');
setOpen(false);
};
return (
<>
<Button type="primary" onClick={showModal}>
Open Modal with async logic
</Button>
<Modal
title="Title"
open={open}
onOk={handleOk}
confirmLoading={confirmLoading}
onCancel={handleCancel}
>
<p>{modalText}</p>
</Modal>
</>
);
};
export default App;
커스터마이즈된 푸터 (Customized Footer)
커스터마이즈된 푸터 버튼 바를 정의하는 더 복잡한 예시예요. 제출 버튼을 클릭한 뒤 대화상자가 로딩 상태로 바뀌고, 로딩이 끝나면 모달이 닫혀요.
기본 푸터 버튼이 필요 없다면 footer를 null로 설정할 수 있어요.
import React, { useState } from 'react';
import { Button, Modal } from 'antd';
const App: React.FC = () => {
const [loading, setLoading] = useState(false);
const [open, setOpen] = useState(false);
const showModal = () => {
setOpen(true);
};
const handleOk = () => {
setLoading(true);
setTimeout(() => {
setLoading(false);
setOpen(false);
}, 3000);
};
const handleCancel = () => {
setOpen(false);
};
return (
<>
<Button type="primary" onClick={showModal}>
Open Modal with customized footer
</Button>
<Modal
open={open}
title="Title"
onOk={handleOk}
onCancel={handleCancel}
footer={[
<Button key="back" onClick={handleCancel}>
Return
</Button>,
<Button key="submit" type="primary" loading={loading} onClick={handleOk}>
Submit
</Button>,
<Button
key="link"
href="https://google.com"
target="_blank"
type="primary"
loading={loading}
onClick={handleOk}
>
Search on Google
</Button>,
]}
>
<p>Some contents...</p>
<p>Some contents...</p>
<p>Some contents...</p>
<p>Some contents...</p>
<p>Some contents...</p>
</Modal>
</>
);
};
export default App;
마스크 (mask)
마스크 효과예요.
import React from 'react';
import { Button, Modal, Space } from 'antd';
const modalConfig = {
title: 'Title',
content: 'Some contents...',
};
const App: React.FC = () => {
const [modal, contextHolder] = Modal.useModal();
return (
<>
<Space>
<Button
onClick={() => {
modal.confirm({ ...modalConfig, mask: { blur: true } });
}}
>
blur
</Button>
<Button
onClick={() => {
modal.confirm(modalConfig);
}}
>
Dimmed mask
</Button>
<Button
onClick={() => {
modal.confirm({ ...modalConfig, mask: false });
}}
>
No mask
</Button>
</Space>
{contextHolder}
</>
);
};
export default App;
로딩 (Loading)
Modal의 로딩 상태를 설정해요.
import React from 'react';
import { Button, Modal } from 'antd';
const App: React.FC = () => {
const [open, setOpen] = React.useState<boolean>(false);
const [loading, setLoading] = React.useState<boolean>(true);
const showLoading = () => {
setOpen(true);
setLoading(true);
// Simple loading mock. You should add cleanup logic in real world.
setTimeout(() => {
setLoading(false);
}, 2000);
};
return (
<>
<Button type="primary" onClick={showLoading}>
Open Modal
</Button>
<Modal
title={<p>Loading Modal</p>}
footer={
<Button type="primary" onClick={showLoading}>
Reload
</Button>
}
loading={loading}
open={open}
onCancel={() => setOpen(false)}
>
<p>Some contents...</p>
<p>Some contents...</p>
<p>Some contents...</p>
</Modal>
</>
);
};
export default App;
커스터마이즈된 푸터 렌더 함수 (Customized Footer render function)
원본 위에 확장할 수 있도록 푸터 렌더링 함수를 커스터마이즈해요.
import React, { useState } from 'react';
import { Button, Modal, Space } from 'antd';
const App: React.FC = () => {
const [open, setOpen] = useState(false);
const showModal = () => {
setOpen(true);
};
const handleOk = () => {
setOpen(false);
};
const handleCancel = () => {
setOpen(false);
};
return (
<>
<Space>
<Button type="primary" onClick={showModal}>
Open Modal
</Button>
<Button
type="primary"
onClick={() => {
Modal.confirm({
title: 'Confirm',
content: 'Bla bla ...',
footer: (_, { OkBtn, CancelBtn }) => (
<>
<Button>Custom Button</Button>
<CancelBtn />
<OkBtn />
</>
),
});
}}
>
Open Modal Confirm
</Button>
</Space>
<Modal
open={open}
title="Title"
onOk={handleOk}
onCancel={handleCancel}
footer={(_, { OkBtn, CancelBtn }) => (
<>
<Button>Custom Button</Button>
<CancelBtn />
<OkBtn />
</>
)}
>
<p>Some contents...</p>
<p>Some contents...</p>
<p>Some contents...</p>
<p>Some contents...</p>
<p>Some contents...</p>
</Modal>
</>
);
};
export default App;
컨텍스트를 얻기 위한 hooks 사용 (Use hooks to get context)
Modal.useModal을 사용하면 컨텍스트에 접근할 수 있는 contextHolder를 얻을 수 있어요. Promise await 연산은 hooks 방식만 지원해요.
import React, { createContext } from 'react';
import { Button, Modal, Space } from 'antd';
const ReachableContext = createContext<string | null>(null);
const UnreachableContext = createContext<string | null>(null);
const config = {
title: 'Use Hook!',
content: (
<>
<ReachableContext.Consumer>{(name) => `Reachable: ${name}!`}</ReachableContext.Consumer>
<br />
<UnreachableContext.Consumer>{(name) => `Unreachable: ${name}!`}</UnreachableContext.Consumer>
</>
),
};
const App: React.FC = () => {
const [modal, contextHolder] = Modal.useModal();
return (
<ReachableContext.Provider value="Light">
<Space>
<Button
onClick={async () => {
const confirmed = await modal.confirm(config);
console.log('Confirmed: ', confirmed);
}}
>
Confirm
</Button>
<Button
onClick={() => {
modal.warning(config);
}}
>
Warning
</Button>
<Button
onClick={async () => {
modal.info(config);
}}
>
Info
</Button>
<Button
onClick={async () => {
modal.error(config);
}}
>
Error
</Button>
</Space>
{/* `contextHolder` should always be placed under the context you want to access */}
{contextHolder}
{/* Can not access this context since `contextHolder` is not in it */}
<UnreachableContext.Provider value="Bamboo" />
</ReachableContext.Provider>
);
};
export default App;
국제화 (Internationalization)
버튼의 텍스트를 커스터마이즈하려면 okText와 cancelText props를 설정해야 해요.
import React, { useState } from 'react';
import { ExclamationCircleOutlined } from '@ant-design/icons';
import { Button, Modal, Space } from 'antd';
const LocalizedModal = () => {
const [open, setOpen] = useState(false);
const showModal = () => {
setOpen(true);
};
const hideModal = () => {
setOpen(false);
};
return (
<>
<Button type="primary" onClick={showModal}>
Modal
</Button>
<Modal
title="Modal"
open={open}
onOk={hideModal}
onCancel={hideModal}
okText="确认"
cancelText="取消"
>
<p>Bla bla ...</p>
<p>Bla bla ...</p>
<p>Bla bla ...</p>
</Modal>
</>
);
};
const App: React.FC = () => {
const [modal, contextHolder] = Modal.useModal();
const confirm = () => {
modal.confirm({
title: 'Confirm',
icon: <ExclamationCircleOutlined />,
content: 'Bla bla ...',
okText: '确认',
cancelText: '取消',
});
};
return (
<>
<Space>
<LocalizedModal />
<Button onClick={confirm}>Confirm</Button>
</Space>
{contextHolder}
</>
);
};
export default App;
수동 업데이트 및 파괴 (Manual to update destroy)
인스턴스를 통해 모달을 수동으로 업데이트하고 파괴해요.
import React from 'react';
import { Button, Modal } from 'antd';
const App: React.FC = () => {
const [modal, contextHolder] = Modal.useModal();
const countDown = () => {
let secondsToGo = 5;
const instance = modal.success({
title: 'This is a notification message',
content: `This modal will be destroyed after ${secondsToGo} second.`,
});
const timer = setInterval(() => {
secondsToGo -= 1;
instance.update({
content: `This modal will be destroyed after ${secondsToGo} second.`,
});
}, 1000);
setTimeout(() => {
clearInterval(timer);
instance.destroy();
}, secondsToGo * 1000);
};
return (
<>
<Button onClick={countDown}>Open modal to close in 5s</Button>
{contextHolder}
</>
);
};
export default App;
모달 위치 커스터마이즈 (To customize the position of modal)
centered, style.top 또는 다른 스타일로 모달 대화상자의 위치를 설정할 수 있어요.
import React, { useState } from 'react';
import { Button, Modal } from 'antd';
const App: React.FC = () => {
const [modal1Open, setModal1Open] = useState(false);
const [modal2Open, setModal2Open] = useState(false);
return (
<>
<Button type="primary" onClick={() => setModal1Open(true)}>
Display a modal dialog at 20px to Top
</Button>
<Modal
title="20px to Top"
style={{ top: 20 }}
open={modal1Open}
onOk={() => setModal1Open(false)}
onCancel={() => setModal1Open(false)}
>
<p>some contents...</p>
<p>some contents...</p>
<p>some contents...</p>
</Modal>
<br />
<br />
<Button type="primary" onClick={() => setModal2Open(true)}>
Vertically centered modal dialog
</Button>
<Modal
title="Vertically centered modal dialog"
centered
open={modal2Open}
onOk={() => setModal2Open(false)}
onCancel={() => setModal2Open(false)}
>
<p>some contents...</p>
<p>some contents...</p>
<p>some contents...</p>
</Modal>
</>
);
};
export default App;
푸터 버튼 props 커스터마이즈 (Customize footer buttons props)
okButtonProps와 cancelButtonProps를 넘기면 OK 버튼과 취소 버튼의 props를 커스터마이즈할 수 있어요.
import React, { useState } from 'react';
import { Button, Modal } from 'antd';
const App: React.FC = () => {
const [open, setOpen] = useState(false);
const showModal = () => {
setOpen(true);
};
const handleOk = () => {
setOpen(false);
};
const handleCancel = () => {
setOpen(false);
};
return (
<>
<Button type="primary" onClick={showModal}>
Open Modal with customized button props
</Button>
<Modal
title="Basic Modal"
open={open}
onOk={handleOk}
onCancel={handleCancel}
okButtonProps={{ disabled: true }}
cancelButtonProps={{ disabled: true }}
>
<p>Some contents...</p>
<p>Some contents...</p>
<p>Some contents...</p>
</Modal>
</>
);
};
export default App;
커스텀 모달 콘텐츠 렌더 (Custom modal content render)
모달 콘텐츠 렌더를 커스터마이즈해요. react-draggable을 사용해 드래그 가능하게 만듭니다.
import React, { useRef, useState } from 'react';
import { Button, ConfigProvider, Modal } from 'antd';
import type { DraggableData, DraggableEvent } from 'react-draggable';
import Draggable from 'react-draggable';
const App: React.FC = () => {
const [open, setOpen] = useState(false);
const [bounds, setBounds] = useState({ left: 0, top: 0, bottom: 0, right: 0 });
const draggleRef = useRef<HTMLDivElement>(null!);
const { getPrefixCls } = React.useContext(ConfigProvider.ConfigContext);
const prefixCls = getPrefixCls('modal');
const dragHandle = `.${prefixCls}-draggable-title`;
const showModal = () => {
setOpen(true);
};
const handleOk = () => {
setOpen(false);
};
const handleCancel = () => {
setOpen(false);
};
const onStart = (_event: DraggableEvent, uiData: DraggableData) => {
const { clientWidth, clientHeight } = window.document.documentElement;
const targetRect = draggleRef.current?.getBoundingClientRect();
if (!targetRect) {
return;
}
setBounds({
left: -targetRect.left + uiData.x,
right: clientWidth - (targetRect.right - uiData.x),
top: -targetRect.top + uiData.y,
bottom: clientHeight - (targetRect.bottom - uiData.y),
});
};
return (
<>
<Button onClick={showModal}>Open Draggable Modal</Button>
<Modal
title="Draggable Modal"
open={open}
onOk={handleOk}
onCancel={handleCancel}
classNames={{ title: dragHandle.slice(1) }}
styles={{ title: { cursor: 'move' } }}
modalRender={(modal) => (
<Draggable
bounds={bounds}
nodeRef={draggleRef}
handle={dragHandle}
onStart={(event, uiData) => onStart(event, uiData)}
>
<div ref={draggleRef}>{modal}</div>
</Draggable>
)}
>
<p>
Just don't learn physics at school and your life will be full of magic and miracles.
</p>
<br />
<p>Day before yesterday I saw a rabbit, and yesterday a deer, and today, you.</p>
</Modal>
</>
);
};
export default App;
모달 너비 커스터마이즈 (To customize the width of modal)
width로 모달 대화상자의 너비를 설정해요.
import React, { useState } from 'react';
import { Button, Flex, Modal } from 'antd';
const App: React.FC = () => {
const [open, setOpen] = useState(false);
const [openResponsive, setOpenResponsive] = useState(false);
return (
<Flex vertical gap="medium" align="flex-start">
{/* Basic */}
<Button type="primary" onClick={() => setOpen(true)}>
Open Modal of 1000px width
</Button>
<Modal
title="Modal 1000px width"
centered
open={open}
onOk={() => setOpen(false)}
onCancel={() => setOpen(false)}
width={1000}
>
<p>some contents...</p>
<p>some contents...</p>
<p>some contents...</p>
</Modal>
{/* Responsive */}
<Button type="primary" onClick={() => setOpenResponsive(true)}>
Open Modal of responsive width
</Button>
<Modal
title="Modal responsive width"
centered
open={openResponsive}
onOk={() => setOpenResponsive(false)}
onCancel={() => setOpenResponsive(false)}
width={{
xs: '90%',
sm: '80%',
md: '70%',
lg: '60%',
xl: '50%',
xxl: '40%',
}}
>
<p>some contents...</p>
<p>some contents...</p>
<p>some contents...</p>
</Modal>
</Flex>
);
};
export default App;
정적 메서드 (Static Method)
정적 메서드는 ConfigProvider가 제공하는 Context를 사용할 수 없어요. layer를 활성화하면 스타일 오류가 발생할 수도 있어요. hooks 버전이나 App에서 제공하는 인스턴스를 우선 사용해 주세요.
import React from 'react';
import { Button, Modal, Space } from 'antd';
const info = () => {
Modal.info({
title: 'This is a notification message',
content: (
<div>
<p>some messages...some messages...</p>
<p>some messages...some messages...</p>
</div>
),
onOk() {},
});
};
const success = () => {
Modal.success({
content: 'some messages...some messages...',
});
};
const error = () => {
Modal.error({
title: 'This is an error message',
content: 'some messages...some messages...',
});
};
const warning = () => {
Modal.warning({
title: 'This is a warning message',
content: 'some messages...some messages...',
});
};
const App: React.FC = () => (
<Space wrap>
<Button onClick={info}>Info</Button>
<Button onClick={success}>Success</Button>
<Button onClick={error}>Error</Button>
<Button onClick={warning}>Warning</Button>
</Space>
);
export default App;
정적 확인 (Static confirmation)
confirm()으로 확인 모달 대화상자를 표시해요. onCancel/onOk 함수가 promise 객체를 반환하도록 하면 대화상자 닫힘을 지연시킬 수 있어요.
import React from 'react';
import { ExclamationCircleFilled } from '@ant-design/icons';
import { Button, Modal, Space } from 'antd';
const { confirm } = Modal;
const showConfirm = () => {
confirm({
title: 'Do you want to delete these items?',
icon: <ExclamationCircleFilled />,
content: 'Some descriptions',
onOk() {
console.log('OK');
},
onCancel() {
console.log('Cancel');
},
});
};
const showPromiseConfirm = () => {
confirm({
title: 'Do you want to delete these items?',
icon: <ExclamationCircleFilled />,
content: 'When clicked the OK button, this dialog will be closed after 1 second',
onOk() {
return new Promise((resolve, reject) => {
setTimeout(Math.random() > 0.5 ? resolve : reject, 1000);
}).catch(() => console.log('Oops errors!'));
},
onCancel() {},
});
};
const showDeleteConfirm = () => {
confirm({
title: 'Are you sure delete this task?',
icon: <ExclamationCircleFilled />,
content: 'Some descriptions',
okText: 'Yes',
okType: 'danger',
cancelText: 'No',
onOk() {
console.log('OK');
},
onCancel() {
console.log('Cancel');
},
});
};
const showPropsConfirm = () => {
confirm({
title: 'Are you sure delete this task?',
icon: <ExclamationCircleFilled />,
content: 'Some descriptions',
okText: 'Yes',
okType: 'danger',
okButtonProps: {
disabled: true,
},
cancelText: 'No',
onOk() {
console.log('OK');
},
onCancel() {
console.log('Cancel');
},
});
};
const App: React.FC = () => (
<Space wrap>
<Button onClick={showConfirm}>Confirm</Button>
<Button onClick={showPromiseConfirm}>With promise</Button>
<Button onClick={showDeleteConfirm} type="dashed">
Delete
</Button>
<Button onClick={showPropsConfirm} type="dashed">
With extra props
</Button>
</Space>
);
export default App;
확인 모달 대화상자 파괴 (destroy confirmation modal dialog)
Modal.destroyAll()은 모든 확인 모달 대화상자를 파괴해요. 보통 라우터 변경 이벤트에서 사용해 확인 모달을 자동으로 파괴할 수 있어요.
import React from 'react';
import { ExclamationCircleOutlined } from '@ant-design/icons';
import { Button, Modal } from 'antd';
const { confirm } = Modal;
const destroyAll = () => {
Modal.destroyAll();
};
const showConfirm = () => {
for (let i = 0; i < 3; i += 1) {
setTimeout(() => {
confirm({
icon: <ExclamationCircleOutlined />,
content: <Button onClick={destroyAll}>Click to destroy all</Button>,
onOk() {
console.log('OK');
},
onCancel() {
console.log('Cancel');
},
});
}, i * 500);
}
};
const App: React.FC = () => <Button onClick={showConfirm}>Confirm</Button>;
export default App;
커스텀 시맨틱 DOM 스타일 (Custom semantic dom styling)
classNames와 styles로 객체나 함수를 넘겨 Modal의 시맨틱 DOM 스타일을 커스터마이즈할 수 있어요.
import React, { useState } from 'react';
import { Button, Flex, Modal } from 'antd';
import type { GetProp, ModalProps } from 'antd';
import { createStaticStyles } from 'antd-style';
const lineStyle: React.CSSProperties = {
lineHeight: '28px',
};
const sharedContent = (
<>
<div style={lineStyle}>
Following the Ant Design specification, we developed a React UI library antd that contains a
set of high quality components and demos for building rich, interactive user interfaces.
</div>
<div style={lineStyle}>🌈 Enterprise-class UI designed for web applications.</div>
<div style={lineStyle}>📦 A set of high-quality React components out of the box.</div>
<div style={lineStyle}>🛡 Written in TypeScript with predictable static types.</div>
<div style={lineStyle}>⚙️ Whole package of design resources and development tools.</div>
<div style={lineStyle}>🌍 Internationalization support for dozens of languages.</div>
<div style={lineStyle}>🎨 Powerful theme customization in every detail.</div>
</>
);
const classNames = createStaticStyles(({ css }) => ({
container: css`
border-radius: 10px;
padding: 10px;
`,
}));
const styles: ModalProps['styles'] = {
mask: {
backgroundImage: `linear-gradient(to top, #18181b 0, rgba(21, 21, 22, 0.2) 100%)`,
},
};
const stylesFn: ModalProps['styles'] = (info): GetProp<ModalProps, 'styles', 'Return'> => {
if (info.props.footer) {
return {
container: {
borderRadius: 14,
border: '1px solid #ccc',
padding: 0,
overflow: 'hidden',
},
header: {
padding: 16,
},
body: {
padding: 16,
},
footer: {
padding: '16px 10px',
backgroundColor: '#fafafa',
},
};
}
return {};
};
const App: React.FC = () => {
const [modalOpen, setModalOpen] = useState(false);
const [modalFnOpen, setModalFnOpen] = useState(false);
const sharedProps: ModalProps = {
centered: true,
classNames,
};
const footer: React.ReactNode = (
<>
<Button
onClick={() => setModalFnOpen(false)}
styles={{ root: { borderColor: '#ccc', color: '#171717', backgroundColor: '#fff' } }}
>
Cancel
</Button>
<Button
type="primary"
styles={{ root: { backgroundColor: '#171717' } }}
onClick={() => setModalOpen(true)}
>
Submit
</Button>
</>
);
return (
<Flex gap="medium">
<Button onClick={() => setModalOpen(true)}>Open Style Modal</Button>
<Button type="primary" onClick={() => setModalFnOpen(true)}>
Open Function Modal
</Button>
<Modal
{...sharedProps}
footer={null}
title="Custom Style Modal"
styles={styles}
open={modalOpen}
onOk={() => setModalOpen(false)}
onCancel={() => setModalOpen(false)}
>
{sharedContent}
</Modal>
<Modal
{...sharedProps}
footer={footer}
title="Custom Function Modal"
styles={stylesFn}
mask={{ enabled: true, blur: true }}
open={modalFnOpen}
onOk={() => setModalFnOpen(false)}
onCancel={() => setModalFnOpen(false)}
>
{sharedContent}
</Modal>
</Flex>
);
};
export default App;
API
공통 props 참조: Common props
| 속성 | 설명 | 타입 | 기본값 | 버전 | 전역 설정 |
|---|---|---|---|---|---|
| afterClose | Modal이 완전히 닫혔을 때 호출되는 함수를 지정 | function | - | × | |
| cancelButtonProps | 취소 버튼의 props | ButtonProps | - | 6.0.0 | |
| cancelText | 취소 버튼의 텍스트 | ReactNode | Cancel |
× | |
| centered | Modal을 세로 중앙에 배치 | boolean | false | 5.24.0 | |
| classNames | Modal 컴포넌트 내 각 시맨틱 구조에 대한 클래스를 커스터마이즈. 객체 또는 함수를 지원 | Record<SemanticDOM, string> | (info: { props }) => Record<SemanticDOM, string> | - | 5.10.0 | |
| closable | 우측 상단에 닫기(x) 버튼을 표시할지 여부 | boolean | ClosableType | true | - | 5.16.0 |
| closeIcon | 커스텀 닫기 아이콘. 5.7.0: null 또는 false로 설정하면 닫기 버튼이 숨겨짐 |
ReactNode | <CloseOutlined /> | 5.14.0 | |
| confirmLoading | OK 버튼에 로딩 시각 효과를 적용할지 여부 | boolean | false | × | |
| 닫을 때 하위 컴포넌트를 마운트 해제할지 여부 | boolean | false | × | ||
| destroyOnHidden | 닫을 때 하위 컴포넌트를 마운트 해제할지 여부 | boolean | false | 5.25.0 | × |
대화상자가 닫힌 후 트리거 요소에 포커스를 줄지 여부. focusable.focusTriggerAfterClose를 대신 사용 |
boolean | true | 4.9.0 | × | |
| footer | 푸터 콘텐츠. 기본 버튼이 필요 없으면 footer={null}로 설정 |
ReactNode | (originNode: ReactNode, extra: { OkBtn: React.FC, CancelBtn: React.FC }) => ReactNode | (OK 및 Cancel 버튼) | renderFunction: 5.9.0 | × |
| forceRender | Modal 강제 렌더링 | boolean | false | × | |
| focusable | Modal에서 포커스 관리를 위한 설정 | { trap?: boolean, focusTriggerAfterClose?: boolean } |
- | 6.2.0 | 6.4.0 |
| getContainer | Modal의 마운트 노드. 여전히 전체 화면에 표시됨 | HTMLElement | () => HTMLElement | Selectors | false | document.body | × | |
| keyboard | esc 키로 닫기 지원 여부 | boolean | true | × | |
| mask | 마스크 효과 | boolean | {enabled?: boolean, blur?: boolean, closable?: boolean} |
true | mask.closable: 6.3.0 | 6.0.0, mask.closable: 6.3.0 |
마스크(모달 바깥 영역)를 클릭해 모달을 닫을지 여부. mask.closable을 대신 사용 |
boolean | true | - | × | |
| modalRender | 커스텀 모달 콘텐츠 렌더 | (node: ReactNode) => ReactNode | - | 4.7.0 | × |
| okButtonProps | OK 버튼의 props | ButtonProps | - | 6.0.0 | |
| okText | OK 버튼의 텍스트 | ReactNode | OK |
× | |
| okType | OK 버튼의 type |
string | primary |
× | |
| style | 떠 있는 레이어의 스타일. 주로 위치 조정에 사용 | CSSProperties | - | 5.7.0 | |
| styles | Modal 컴포넌트 내 각 시맨틱 구조에 대한 인라인 스타일을 커스터마이즈. 객체 또는 함수를 지원 | Record<SemanticDOM, CSSProperties> | (info: { props })=> Record<SemanticDOM, CSSProperties> | - | 5.10.0 | |
| loading | Skeleton 표시 | boolean | 5.18.0 | × | |
| scrollLock | Modal이 열릴 때 body 스크롤을 잠글지 여부 | boolean | true | 6.5.0 | × |
| title | 모달 대화상자의 제목 | ReactNode | - | × | |
| open | 모달 대화상자의 표시 여부 | boolean | false | × | |
| width | 모달 대화상자의 너비 | string | number | Breakpoint | 520 | Breakpoint: 5.23.0 | × |
| wrapClassName | 모달 대화상자 컨테이너의 클래스 이름 | string | - | × | |
| zIndex | Modal의 z-index |
number | 1000 | × | |
| onCancel | 사용자가 마스크, 우측 상단 닫기 버튼 또는 취소 버튼을 클릭했을 때 호출되는 함수를 지정 | function(e) | - | × | |
| onOk | 사용자가 OK 버튼을 클릭했을 때 호출되는 함수를 지정 | function(e) | - | × | |
| afterOpenChange | Modal이 켜지고 꺼질 때 애니메이션이 끝나는 시점의 콜백 | (open: boolean) => void | - | 5.4.0 | × |
참고 (Note)
- Modal의 상태는 기본적으로 컴포넌트 생명주기 동안 유지돼요. 매번 완전히 새로운 상태로 열고 싶다면
destroyOnHidden을 설정하세요. <Modal />을 Form과 함께 사용하면destroyOnHidden을 설정해도 Modal을 닫을 때 필드 값이 지워지지 않는 상황이 있어요. 이 경우<Form preserve={false} />가 필요해요.Modal.method()의 RTL 모드는 hooks만 지원해요.
Modal.method()
콘텐츠의 성격에 따라 정보를 표시하는 다섯 가지 방법이 있어요.
Modal.infoModal.successModal.errorModal.warningModal.confirm
위 항목은 모두 함수이며, 설정 객체를 파라미터로 받아요. 객체의 속성은 다음과 같아요.
| 속성 | 설명 | 타입 | 기본값 | 버전 |
|---|---|---|---|---|
| afterClose | Modal이 완전히 닫혔을 때 호출되는 함수를 지정 | function | - | 4.9.0 |
자동 포커스할 버튼 지정. focusable.autoFocusButton을 대신 사용 |
null | ok | cancel |
ok |
||
| cancelButtonProps | 취소 버튼의 props | ButtonProps | - | |
| cancelText | Modal.confirm의 취소 버튼 텍스트 | string | Cancel |
|
| centered | Modal을 세로 중앙에 배치 | boolean | false | |
| className | 컨테이너의 className | string | - | |
| closable | 확인 대화상자 우측 상단에 닫기(x) 버튼을 표시할지 여부 | boolean | ClosableType | false | - |
| closeIcon | 커스텀 닫기 아이콘 | ReactNode | undefined | 4.9.0 |
| content | 콘텐츠 | ReactNode | - | |
| focusable.autoFocusButton | 자동 포커스할 버튼 지정 | null | ok | cancel |
ok |
6.2.0 |
| footer | 푸터 콘텐츠. 기본 버튼이 필요 없으면 footer: null로 설정 |
ReactNode | (originNode: ReactNode, extra: { OkBtn: React.FC, CancelBtn: React.FC }) => ReactNode | - | renderFunction: 5.9.0 |
| getContainer | Modal의 마운트 노드 반환 | HTMLElement | () => HTMLElement | Selectors | false | document.body | |
| icon | 커스텀 아이콘 | ReactNode | <ExclamationCircleFilled /> | |
| keyboard | esc 키로 닫기 지원 여부 | boolean | true | |
| mask | 마스크 효과 | boolean | {enabled?: boolean, blur?: boolean, closable?: boolean} |
true | |
마스크(모달 바깥 영역)를 클릭해 모달을 닫을지 여부. mask.closable을 대신 사용 |
boolean | false | - | |
| scrollLock | Modal이 열릴 때 body 스크롤을 잠글지 여부 | boolean | true | 6.5.0 |
| okButtonProps | OK 버튼의 props | ButtonProps | - | |
| okText | OK 버튼의 텍스트 | string | OK |
|
| okType | OK 버튼의 type |
string | primary |
|
| style | 떠 있는 레이어의 스타일. 주로 위치 조정에 사용 | CSSProperties | - | |
| title | 제목 | ReactNode | - | |
| width | 모달 대화상자의 너비 | string | number | 416 | |
| wrapClassName | 모달 대화상자 컨테이너의 클래스 이름 | string | - | 4.18.0 |
| zIndex | Modal의 z-index |
number | 1000 | |
| onCancel | onCancel 콜백 클릭. 파라미터는 닫는 함수이고, promise를 반환하면 resolve는 정상 닫기, reject는 닫지 않음을 의미 | function(close) | - | |
| onOk | onOk 콜백 클릭. 파라미터는 닫는 함수이고, promise를 반환하면 resolve는 정상 닫기, reject는 닫지 않음을 의미 | function(close) | - |
모든 Modal.method는 참조를 반환하며, 이를 통해 모달 대화상자를 업데이트하고 닫을 수 있어요.
ClosableType
| 속성 | 설명 | 타입 | 기본값 | 버전 |
|---|---|---|---|---|
| afterClose | Modal이 완전히 닫혔을 때 호출되는 함수를 지정 | function | - | - |
| closeIcon | 커스텀 닫기 아이콘 | ReactNode | undefined | - |
| disabled | 닫기 아이콘 비활성화 여부 | boolean | false | - |
| onClose | Modal이 닫힐 때 트리거 | Function | undefined | - |
const modal = Modal.info();
modal.update({
title: 'Updated title',
content: 'Updated content',
});
// on 4.8.0 or above, you can pass a function to update modal
modal.update((prevConfig) => ({
...prevConfig,
title: `${prevConfig.title} (New)`,
}));
modal.destroy();
Modal.destroyAll
Modal.destroyAll()은 모든 확인 모달 대화상자(Modal.confirm|success|info|error|warning)를 파괴할 수 있어요. 보통 라우터 변경 이벤트에서 모달 참조를 사용하지 않고 확인 모달을 자동으로 파괴할 때 쓰면 편해요.
import { browserHistory } from 'react-router';
// router change
browserHistory.listen(() => {
Modal.destroyAll();
});
Modal.useModal()
Context를 사용해야 할 때는 Modal.useModal이 만든 contextHolder를 children에 삽입할 수 있어요. hooks로 만든 Modal은 contextHolder가 있는 위치의 모든 컨텍스트를 얻어요. 만들어진 modal은 Modal.method와 같은 생성 함수를 가져요.
const [modal, contextHolder] = Modal.useModal();
React.useEffect(() => {
modal.confirm({
// ...
});
}, []);
return <div>{contextHolder}</div>;
modal.confirm은 다음 메서드를 반환해요.
destroy: 현재 모달 파괴update: 현재 모달 업데이트then: (hooks 전용) Promise 체인 호출,await연산 지원
// `onOk`를 클릭하면 `true`를, `onCancel`을 클릭하면 `false`를 반환합니다
const confirmed = await modal.confirm({ ... });
Semantic DOM
https://ant.design/components/modal/semantic.md
Design Token
Component Token (Modal)
| 토큰 이름 | 설명 | 타입 | 기본값 |
|---|---|---|---|
| contentBg | 콘텐츠의 배경색 | string | #ffffff |
| footerBg | 푸터의 배경색 | string | transparent |
| headerBg | 헤더의 배경색 | string | transparent |
| titleColor | 제목의 글꼴 색상 | string | rgba(0,0,0,0.88) |
| titleFontSize | 제목의 글꼴 크기 | string | number | 16 |
| titleLineHeight | 제목의 줄 높이 | string | number | 1.5 |
Global Token
| 토큰 이름 | 설명 | 타입 | 기본값 |
|---|---|---|---|
| borderRadiusLG | LG 크기의 테두리 반경. Card, Modal 등 큰 테두리 반경 컴포넌트에서 사용 | number | |
| borderRadiusSM | SM 크기의 테두리 반경. 작은 크기 입력 컴포넌트(Button, Input, Select 등)에서 사용 | number | |
| boxShadow | 요소의 박스 섀도 스타일 제어 | string | |
| colorBgMask | 마스크의 배경색. 마스크 아래 콘텐츠를 덮는 데 사용하며, Modal, Drawer, Image 등 컴포넌트가 이 토큰 사용 | string | |
| colorBgTextActive | 활성 상태 텍스트의 배경색 제어 | string | |
| colorBgTextHover | 호버 상태 텍스트의 배경색 제어 | string | |
| colorIcon | 약한 동작. allowClear나 Alert 닫기 버튼 등 |
string | |
| colorIconHover | 약한 동작의 호버 색상. allowClear나 Alert 닫기 버튼 등 |
string | |
| colorPrimaryBorder | 메인 색 그라데이션의 획 색상. Slider 등 컴포넌트의 획에 사용 | string | |
| colorSplit | 구분선의 색상으로 사용. colorBorderSecondary와 같지만 투명도를 가짐 | string | |
| colorText | W3C 표준을 따르는 기본 텍스트 색상. 가장 어두운 중립 색 | string | |
| controlHeight | Ant Design에서 버튼, 입력 상자 등 기본 컨트롤의 높이 | number | |
| fontFamily | Ant Design의 글꼴 패밀리는 시스템 기본 인터페이스 글꼴을 우선하며, 화면 표시에 적합한 대체 글꼴 라이브러리를 제공 | string | |
| fontSize | 디자인 시스템에서 가장 널리 쓰이는 글꼴 크기. 텍스트 그라데이션이 여기서 파생됨 | number | |
| fontSizeHeading5 | h5 태그의 글꼴 크기 | string | number | |
| fontSizeLG | 큰 글꼴 크기 | number | |
| fontWeightStrong | 제목 컴포넌트(h1, h2, h3)나 선택된 항목의 글꼴 두께 제어 | number | |
| lineHeight | 텍스트의 줄 높이 | number | |
| lineHeightHeading5 | h5 태그의 줄 높이 | number | |
| lineType | 기본 컴포넌트의 테두리 스타일 | string | |
| lineWidth | 기본 컴포넌트의 테두리 너비 | number | |
| lineWidthFocus | 컴포넌트가 포커스 상태일 때 라인의 너비 제어 | number | |
| margin | 요소의 여백을 중간 크기로 제어 | number | |
| marginXS | 요소의 여백을 작은 크기로 제어 | number | |
| motionDurationMid | 중간 속도 모션. 중간 요소 애니메이션 상호작용에 사용 | string | |
| motionDurationSlow | 느린 속도 모션. 큰 요소 애니메이션 상호작용에 사용 | string | |
| motionEaseInOutCirc | 사전 설정된 모션 커브 | string | |
| motionEaseOutCirc | 사전 설정된 모션 커브 | string | |
| padding | 요소의 패딩 제어 | number | |
| screenLGMin | 대형 화면의 최소 너비 제어 | number | |
| screenMDMin | 중형 화면의 최소 너비 제어 | number | |
| screenSMMax | 소형 화면의 최대 너비 제어 | number | |
| screenSMMin | 소형 화면의 최소 너비 제어 | number | |
| screenXLMin | 대형 화면 이상의 최소 너비 제어 | number | |
| screenXSMin | 초소형 화면의 최소 너비 제어 | number | |
| screenXXLMin | 초대형 화면의 최소 너비 제어 | number | |
| screenXXXLMin | XXXL 화면의 최소 너비 제어 | number | |
| zIndexPopupBase | FloatButton, Affix처럼 큰 팝업이 덮을 수 있는 컴포넌트의 기본 zIndex | number |
FAQ
Modal을 닫았을 때 콘텐츠가 업데이트되지 않는 이유는 뭔가요? {#faq-content-not-update}
Modal은 닫힐 때 콘텐츠 점프를 막기 위해 memo를 사용해요. 또한 Modal에서 Form을 사용한다면 effect에서 resetFields를 호출해 initialValues를 재설정할 수 있어요.
Modal.xxx에서 context, redux, ConfigProvider locale/prefixCls에 접근할 수 없는 이유는 뭔가요? {#faq-context-redux}
Modal 메서드를 호출하면 antd가 ReactDOM.render로 React 인스턴스를 동적으로 생성해요. 이 인스턴스의 컨텍스트는 원래 코드가 위치한 컨텍스트와 달라요.
(ConfigProvider context 같은) 컨텍스트 정보가 필요하다면 Modal.useModal을 사용해 modal 인스턴스와 contextHolder 노드를 얻은 뒤 children에 넣어 주세요.
const [modal, contextHolder] = Modal.useModal();
// then call modal.confirm instead of Modal.confirm
return (
<Context1.Provider value="Ant">
{/* contextHolder is in Context1, which means modal will get context of Context1 */}
{contextHolder}
<Context2.Provider value="Design">
{/* contextHolder is out of Context2, which means modal will not get context of Context2 */}
</Context2.Provider>
</Context1.Provider>
);
참고: hooks를 사용한다면 contextHolder를 반드시 children에 넣어야 해요. 컨텍스트 연결이 필요 없다면 원래 메서드를 그대로 써도 돼요.
App Package 컴포넌트를 쓰면
useModal등contextHolder를 수동으로 심어야 하는 메서드의 문제를 간단히 해결할 수 있어요.
정적 메서드의 prefixCls를 어떻게 설정하나요? {#faq-set-prefix-cls}
ConfigProvider.config로 설정할 수 있어요.