Skip to main content

Modal

See React Bootstrap/Modals for full documentation and props.

The Modal component can have many possible variations. The most basic usage is to render a Modal with all content in the body and an optional Modal.Icon.

The Modal.Icon component can by styled by providing a variant prop. The default variant is 'info'.

Result
Loading...
Live Editor
function Example() {
	const [show, setShow] = React.useState(false);

	return (
		<>
			<Button onClick={() => setShow(true)}>Show modal</Button>

			<Modal show={show} onHide={() => setShow(false)} size="lg">
				<Modal.Body>
					<div className="d-flex">
						<div className="me-3">
							<Modal.Icon>
								<span className="icon-message-circle-02" />
							</Modal.Icon>
						</div>
						<div>
							<strong className="fs-xxl">Title</strong>
							<br />
							Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
							incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud
							exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
						</div>
					</div>
				</Modal.Body>

				<Modal.Footer>
					<Button variant="primary" onClick={() => setShow(false)}>
						Dismiss
					</Button>
				</Modal.Footer>
			</Modal>
		</>
	);
}

When using the Modal.Header component the Modal.Icon should be placed within the header to the left of the Modal.Title.

Result
Loading...
Live Editor
function Example() {
	const [show, setShow] = React.useState(false);

	return (
		<div data-testid="pw-modal-header">
			<Button onClick={() => setShow(true)}>Show modal</Button>

			<Modal show={show} onHide={() => setShow(false)} size="lg">
				<Modal.Header closeButton>
					<div className="d-flex">
						<div className="me-3">
							<Modal.Icon>
								<span className="icon-message-circle-02" />
							</Modal.Icon>
						</div>

						<Modal.Title>Title</Modal.Title>
					</div>
				</Modal.Header>
				<Modal.Body>
					Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt
					ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation
					ullamco laboris nisi ut aliquip ex ea commodo consequat.
				</Modal.Body>

				<Modal.Footer>
					<span className="text-gray-600 me-3">Line of context for the buttons</span>
					<Button variant="outline-secondary" onClick={() => setShow(false)}>
						Cancel
					</Button>
					<Button variant="primary" onClick={() => setShow(false)}>
						Save
					</Button>
				</Modal.Footer>
			</Modal>
		</div>
	);
}

Centered and stacked

This variation of the Modal moves the content of the footer into the body and centers all content vertically. Using the d-grid utility class around Buttons will force them to take the full width of their container.

Result
Loading...
Live Editor
function Example() {
	const [show, setShow] = React.useState(false);

	return (
		<>
			<Button onClick={() => setShow(true)}>Show modal</Button>

			<Modal show={show} onHide={() => setShow(false)} centered>
				<Modal.Body>
					<div className="d-flex flex-column align-items-center">
						<div className="mb-3">
							<Modal.Icon variant="danger">
								<span className="icon-alert-triangle" />
							</Modal.Icon>
						</div>
						<div className="mb-3">
							<strong className="fs-xxl">Deactivate account</strong>
						</div>
					</div>

					<div className="text-center mb-5">
						Are you sure you want to deactivate your account? All of your data will be permanently
						removed from our servers forever. This action cannot be undone.
					</div>

					<div className="d-grid">
						<Button variant="outline-secondary" className="mb-3" onClick={() => setShow(false)}>
							Cancel
						</Button>
						<Button variant="primary" onClick={() => setShow(false)}>
							Deactivate
						</Button>
					</div>
				</Modal.Body>
			</Modal>
		</>
	);
}

Interactive footer content (checkboxes or links) should be positioned to the start of the footer. The buttons should be remain positioned to the end of the footer.

Result
Loading...
Live Editor
function Example() {
	const [show, setShow] = React.useState(false);

	return (
		<>
			<Button onClick={() => setShow(true)}>Show modal</Button>

			<Modal show={show} onHide={() => setShow(false)} size="lg">
				<Modal.Header closeButton>
					<div className="d-flex">
						<div className="me-3">
							<Modal.Icon>
								<span className="icon-user-circle" />
							</Modal.Icon>
						</div>

						<Modal.Title>Save account</Modal.Title>
					</div>
				</Modal.Header>

				<Modal.Body>
					Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt
					ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation
					ullamco laboris nisi ut aliquip ex ea commodo consequat.
				</Modal.Body>

				<Modal.Footer className="d-flex justify-content-between">
					<div>
						<Checkbox label="Remember me" />
					</div>
					<div>
						<Button variant="outline-secondary" onClick={() => setShow(false)}>
							Cancel
						</Button>
						<Button variant="primary" onClick={() => setShow(false)}>
							Save
						</Button>
					</div>
				</Modal.Footer>
			</Modal>
		</>
	);
}

Using a form ref in Modal.Footer

To make sure there are not duplicate footers when using a Form inside a Modal, pass a function that returns null into the Form's footerRenderer prop. A ref to the Form can be used to call submit when one of the buttons in the Modal's footer is pressed.

Result
Loading...
Live Editor
function Example() {
	const formRef = React.useRef(null);
	const [show, setShow] = React.useState(false);

	const schema = object().shape({
		awesomeField: string().required(),
	});

	return (
		<>
			<Button onClick={() => setShow(true)}>Show modal</Button>

			<Modal show={show} onHide={() => setShow(false)} size="lg">
				<Modal.Header closeButton>
					<div className="d-flex">
						<div className="me-3">
							<Modal.Icon>
								<span className="icon-pencil-01" />
							</Modal.Icon>
						</div>

						<Modal.Title>Modal Form</Modal.Title>
					</div>
				</Modal.Header>

				<Modal.Body>
					<Form
						ref={formRef}
						onSubmit={(values, helpers) => {
							console.log({ values, helpers });
							setShow(false);
						}}
						footerRenderer={() => null}
						validationSchema={schema}
					>
						<FormField>
							<TextField name="awesomeField" label="Awesome Field" />
						</FormField>
					</Form>
				</Modal.Body>

				<Modal.Footer>
					<Button variant="primary" onClick={() => formRef.current.submitForm()}>
						Submit
					</Button>
				</Modal.Footer>
			</Modal>
		</>
	);
}

It's also possible to wrap the contents of the modal in a Form and use the footerRenderer prop to render the buttons in the footer. This way, the form can be submitted by calling the onSubmit function passed to the footerRenderer prop.

Result
Loading...
Live Editor
function Example() {
	const [show, setShow] = React.useState(false);

	const schema = object().shape({
		awesomeField: string().required(),
	});

	return (
		<>
			<Button onClick={() => setShow(true)}>Show modal</Button>

			<Modal show={show} onHide={() => setShow(false)} size="lg">
				<Form
					onSubmit={(values, helpers) => {
						console.log({ values, helpers });
						setShow(false);
					}}
					footerRenderer={(form) => (
						<Modal.Footer>
							<Button variant="primary" onClick={() => form.onSubmit()}>
								Submit
							</Button>
						</Modal.Footer>
					)}
					validationSchema={schema}
				>
					<Modal.Header closeButton>
						<div className="d-flex">
							<div className="me-3">
								<Modal.Icon>
									<span className="icon-pencil-01" />
								</Modal.Icon>
							</div>

							<Modal.Title>Modal Form</Modal.Title>
						</div>
					</Modal.Header>

					<Modal.Body>
						<FormField>
							<TextField name="awesomeField" label="Awesome Field" />
						</FormField>
					</Modal.Body>
				</Form>
			</Modal>
		</>
	);
}

Using Tabs

Result
Loading...
Live Editor
function Example() {
	const [show, setShow] = useState(false);
	const [activeTabId, setActiveTabId] = useState('Ownership');

	const handleClose = () => setShow(false);
	const handleShow = () => setShow(true);

	const tabsList = [
		<Tab key="Ownership" id="Ownership">
			Ownership
		</Tab>,
		<Tab key="Fees" id="Fees">
			Fees
		</Tab>,
		<Tab key="Realization" id="Realization">
			Realization
		</Tab>,
		<Tab key="Holdings" id="Holdings">
			Holdings
		</Tab>,
	];

	const tabContents = useMemo(() => {
		switch (activeTabId) {
			case 'Ownership':
				return 'Ownership Content';
			case 'Fees':
				return 'Fees Content';
			case 'Realization':
				return 'Realization Content';
			case 'Holdings':
				return 'Holdings Content';
			default:
				return null;
		}
	}, [activeTabId]);

	return (
		<>
			<Button variant="primary" onClick={handleShow}>
				Toggle
			</Button>

			<Modal show={show} onHide={handleClose}>
				<Modal.Header className="border-0" closeButton>
					<div className="d-flex">
						<div className="me-3">
							<Modal.Icon>
								<span className="icon-placeholder" />
							</Modal.Icon>
						</div>

						<Modal.Title>Modal With Tabs</Modal.Title>
					</div>
				</Modal.Header>
				<Modal.Body className="px-0 pt-0">
					<Tabs
						variant="tabs"
						tabs={tabsList}
						activeTabId={activeTabId}
						onTabClick={(event, id) => {
							setActiveTabId(id);
						}}
						className="k-px-3"
					></Tabs>
					<div className="border-top k-p-3" style={{ marginTop: 1 }}>
						{tabContents}
					</div>
				</Modal.Body>
				<Modal.Footer>
					<Button onClick={handleClose} variant="outline-secondary">
						Cancel
					</Button>
					<Button onClick={handleClose}>Save</Button>
				</Modal.Footer>
			</Modal>
		</>
	);
}

Analytics

The Modal component is trackable through Kyber Analytics. This is the default analytics config.

export default {
value: 'Modal',
actions: {
onHide: { type: 'MODAL_ON_HIDE', payload: 'Hide' },
},
};

Props

ModalIcon