Modal

An accessible overlay dialog
Import

Usage

import { useDisclosure } from '@mantine/hooks';
import { Modal, Button, Group } from '@mantine/core';
function Demo() {
const [opened, { open, close }] = useDisclosure(false);
return (
<>
<Modal opened={opened} onClose={close} title="Authentication">
{/* Modal content */}
</Modal>
<Group position="center">
<Button onClick={open}>Open modal</Button>
</Group>
</>
);
}

Center modal vertically

import { useDisclosure } from '@mantine/hooks';
import { Modal, Group, Button } from '@mantine/core';
function Demo() {
const [opened, { open, close }] = useDisclosure(false);
return (
<>
<Modal opened={opened} onClose={close} title="Authentication" centered>
{/* Modal content */}
</Modal>
<Group position="center">
<Button onClick={open}>Open centered Modal</Button>
</Group>
</>
);
}

Remove header

To remove header set withCloseButton={false}

import { useDisclosure } from '@mantine/hooks';
import { Modal, Group, Button } from '@mantine/core';
function Demo() {
const [opened, { open, close }] = useDisclosure(false);
return (
<>
<Modal opened={opened} onClose={close} withCloseButton={false}>
Modal without header, press escape or click on overlay to close
</Modal>
<Group position="center">
<Button onClick={open}>Open Modal</Button>
</Group>
</>
);
}

Change size

You can change modal width by setting size prop to predefined size or any valid width, for example, 55% or 50rem. Modal width cannot exceed 100vw.

<Modal size="sm" /> // -> predefined small size
<Modal size={320} /> // -> size in rem –> 320 -> 20rem
<Modal size="55%" /> // -> size in %
<Modal size="calc(100vw - 3rem)" /> // -> size with calc

Size auto

Modal with size="auto" will have width to fit its content:

import { useDisclosure, useCounter } from '@mantine/hooks';
import { Modal, Button, Group, Text, Badge } from '@mantine/core';
function Demo() {
const [opened, { close, open }] = useDisclosure(false);
const [count, { increment, decrement }] = useCounter(3, { min: 0 });
const badges = Array(count)
.fill(0)
.map((_, index) => <Badge key={index}>Badge {index}</Badge>);
return (
<>
<Modal opened={opened} onClose={close} size="auto" title="Modal size auto">
<Text>Modal with size auto will fits its content</Text>
<Group noWrap mt="md">
{badges}
</Group>
<Group mt="xl">
<Button variant="outline" onClick={increment}>
Add badge
</Button>
<Button variant="outline" onClick={decrement}>
Remove badge
</Button>
</Group>
</Modal>
<Group position="center">
<Button onClick={open}>Open modal</Button>
</Group>
</>
);
}

Fullscreen

Fullscreen modal will take the entire screen, it is usually to change transition to fade when fullScreen prop is set:

import { useDisclosure } from '@mantine/hooks';
import { Modal, Button, Group } from '@mantine/core';
function Demo() {
const [opened, { open, close }] = useDisclosure(false);
return (
<>
<Modal
opened={opened}
onClose={close}
title="This is a fullscreen modal"
fullScreen
transitionProps={{ transition: 'fade', duration: 200 }}
>
{/* Modal content */}
</Modal>
<Group position="center">
<Button onClick={open}>Open Modal</Button>
</Group>
</>
);
}

To switch Modal to fullscreen on devices with small screens only use use-media-query hook. size prop is ignored if fullScreen prop is set:

import { useDisclosure, useMediaQuery } from '@mantine/hooks';
import { Modal, Button, Group } from '@mantine/core';
function Demo() {
const [opened, { open, close }] = useDisclosure(false);
const isMobile = useMediaQuery("(max-width: 50em)");
return (
<>
<Modal
opened={opened}
onClose={close}
title="This is a fullscreen modal"
fullScreen={isMobile}
transitionProps={{ transition: 'fade', duration: 200 }}
>
The Modal will be full screen only on mobile
</Modal>
<Group position="center">
<Button onClick={open}>Open Modal</Button>
</Group>
</>
);
}

Customize overlay

Modal uses Overlay component, you can set any props that Overlay supports with overlayProps:

import { useDisclosure } from '@mantine/hooks';
import { Modal, useMantineTheme } from '@mantine/core';
function Demo() {
const [opened, { open, close }] = useDisclosure(false);
const theme = useMantineTheme();
return (
<>
<Modal
opened={opened}
onClose={close}
title="Authentication"
overlayProps={{
color: theme.colorScheme === 'dark' ? theme.colors.dark[9] : theme.colors.gray[2],
opacity: 0.55,
blur: 3,
}}
>
{/* Modal content */}
</Modal>
<Group position="center">
<Button onClick={open}>Open modal</Button>
</Group>
</>
);
}

Modal with scroll

import { useDisclosure } from '@mantine/hooks';
import { Modal, Group, Button } from '@mantine/core';
function Demo() {
const [opened, { open, close }] = useDisclosure(false);
const content = Array(100)
.fill(0)
.map((_, index) => <p key={index}>Modal with scroll</p>);
return (
<>
<Modal opened={opened} onClose={close} title="Header is sticky">
{content}
</Modal>
<Group position="center">
<Button onClick={open}>Open modal</Button>
</Group>
</>
);
}

Usage with ScrollArea

import { useDisclosure } from '@mantine/hooks';
import { Modal, Group, Button, ScrollArea } from '@mantine/core';
function Demo() {
const [opened, { open, close }] = useDisclosure(false);
const content = Array(100)
.fill(0)
.map((_, index) => <p key={index}>Modal with scroll</p>);
return (
<>
<Modal
opened={opened}
onClose={close}
title="Header is sticky"
scrollAreaComponent={ScrollArea.Autosize}
>
{content}
</Modal>
<Group position="center">
<Button onClick={open}>Open modal</Button>
</Group>
</>
);
}

Change offsets

Use xOffset/yOffset to configure horizontal/vertical content offsets:

import { useDisclosure } from '@mantine/hooks';
import { Modal, Button, Group } from '@mantine/core';
function Demo() {
const [opened, { open, close }] = useDisclosure(false);
return (
<>
<Modal opened={opened} onClose={close} title="Authentication" yOffset="1vh" xOffset={0}>
{/* Modal content */}
</Modal>
<Group position="center">
<Button onClick={open}>Open modal</Button>
</Group>
</>
);
}

Change transitions

Modal is built with Transition component. Use transitionProps prop to customize any Transition properties:

import { useState } from 'react';
import { Modal, Group, Button } from '@mantine/core';
function Demo() {
const [noTransitionOpened, setNoTransitionOpened] = useState(false);
const [slowTransitionOpened, setSlowTransitionOpened] = useState(false);
return (
<>
<Modal
opened={slowTransitionOpened}
onClose={() => setSlowTransitionOpened(false)}
title="Please consider this"
transitionProps={{ transition: 'rotate-left' }}
>
rotate-left transition
</Modal>
<Modal
opened={noTransitionOpened}
onClose={() => setNoTransitionOpened(false)}
title="Please consider this"
transitionProps={{ transition: 'fade', duration: 600, timingFunction: 'linear' }}
>
fade transition 600ms linear transition
</Modal>
<Group position="center">
<Button onClick={() => setSlowTransitionOpened(true)} color="pink">
Rotate left transition
</Button>
<Button onClick={() => setNoTransitionOpened(true)} color="cyan">
Fade transition
</Button>
</Group>
</>
);
}

Initial focus

Modal uses FocusTrap to trap focus. Add data-autofocus attribute to the element that should receive initial focus.

import { useDisclosure } from '@mantine/hooks';
import { Modal, Group, Button, TextInput } from '@mantine/core';
function Demo() {
const [opened, { open, close }] = useDisclosure(false);
return (
<>
<Modal opened={opened} onClose={close} title="Focus demo">
<TextInput label="First input" placeholder="First input" />
<TextInput
data-autofocus
label="Input with initial focus"
placeholder="It has data-autofocus attribute"
mt="md"
/>
</Modal>
<Group position="center">
<Button onClick={open}>Open modal</Button>
</Group>
</>
);
}

Control behavior

The following props can be used to control Modal behavior. In most cases it is not recommended to turn these features off – it will make the component less accessible.

  • trapFocus – determines whether focus should be trapped inside modal
  • closeOnEscape – determines whether the modal should be closed when Escape key is pressed
  • closeOnClickOutside – determines whether the modal should be closed when user clicks on the overlay
  • returnFocus – determines whether focus should be returned to the element that was focused before the modal was opened

Compound components

You can use the following compound components to have full control over the Modal rendering:

  • Modal.Root – context provider
  • Modal.Overlay – render Overlay
  • Modal.Content – main modal element, should include all modal content
  • Modal.Header – sticky header, usually contains Modal.Title and Modal.CloseButton
  • Modal.Titleh2 element, aria-labelledby of Modal.Content is pointing to this element, usually is rendered inside Modal.Header
  • Modal.CloseButton – close button, usually rendered inside Modal.Header
  • Modal.Body – a place for main content, aria-describedby of Modal.Content is pointing to this element
import { useDisclosure } from '@mantine/hooks';
import { Modal, Button, Group } from '@mantine/core';
function Demo() {
const [opened, { open, close }] = useDisclosure(false);
return (
<>
<Modal.Root opened={opened} onClose={close}>
<Modal.Overlay />
<Modal.Content>
<Modal.Header>
<Modal.Title>Modal title</Modal.Title>
<Modal.CloseButton />
</Modal.Header>
<Modal.Body>Modal content</Modal.Body>
</Modal.Content>
</Modal.Root>
<Group position="center">
<Button onClick={open}>Open modal</Button>
</Group>
</>
);
}

Fixed elements offset

Modal component uses react-remove-scroll package to lock scroll. To properly size these elements add a className to them (documentation):

import { RemoveScroll } from '@mantine/core';
// to make "width: 100%"
<div className={RemoveScroll.classNames.fullWidth} />
// to make "right: 0"
<div className={RemoveScroll.classNames.zeroRight} />

Accessibility

Modal component follows WAI-ARIA recommendations on accessibility.

Labels

Set title props to make component accessible, will add aria-labelledby to the content element:

import { Modal } from '@mantine/core';
function Demo() {
return <Modal title="Modal label" opened onClose={() => {}} />;
}

To set close button aria-label use closeButtonProps:

import { Modal } from '@mantine/core';
function Demo() {
return <Modal closeButtonProps={{ 'aria-label': 'Close modal' }} opened onClose={() => {}} />;
}

Keyboard interactions

KeyDescription
EscapeClose modal

Modal component props

NameTypeDescription
centered
boolean
Determines whether the modal should be centered vertically, false by default
children
ReactNode
Modal content
closeButtonProps
ModalBaseCloseButtonProps
Props added to close button
closeOnClickOutside
boolean
Determines whether the modal/drawer should be closed when user clicks on the overlay, true by default
closeOnEscape
boolean
Determines whether onClose should be called when user presses escape key, true by default
fullScreen
boolean
Determines whether the modal should take the entire screen
id
string
Id used to connect modal/drawer with body and title
keepMounted
boolean
If set modal/drawer will not be unmounted from the DOM when it is hidden, display: none styles will be added instead
lockScroll
boolean
Determines whether scroll should be locked when opened={true}, defaults to true
onClose *
() => void
Called when modal/drawer is closed
opened *
boolean
Determines whether modal/drawer is opened
overlayProps
ModalBaseOverlayProps
Props added to Overlay component, use configure opacity, background color, styles and other properties
padding
number | "xs" | "sm" | "md" | "lg" | "xl"
Key of theme.spacing or any valid CSS value to set content, header and footer padding, 'md' by default
portalProps
Omit<PortalProps, "children" | "target" | "withinPortal">
Props to pass down to the portal when withinPortal is true
radius
number | "xs" | "sm" | "md" | "lg" | "xl"
Key of theme.radius or any valid CSS value to set border-radius, theme.defaultRadius by default
returnFocus
boolean
Determines whether focus should be returned to the last active element onClose is called, true by default
scrollAreaComponent
ScrollAreaComponent
Scroll area component, ScrollArea.Autosize by default
shadow
MantineShadow
Key of theme.shadows or any valid css box-shadow value, 'xl' by default
size
number | "xs" | "sm" | "md" | "lg" | "xl"
Controls content width, 'md' by default
target
string | HTMLElement
Target element or selector where Portal should be rendered, by default new element is created and appended to the document.body
title
ReactNode
Modal title
transitionProps
Partial<Omit<TransitionProps, "mounted">>
Props added to Transition component that used to animate overlay and body, use to configure duration and animation type, { duration: 200, transition: 'pop' } by default
trapFocus
boolean
Determines whether focus should be trapped, true by default
withCloseButton
boolean
Determines whether close button should be rendered, true by default
withOverlay
boolean
Determines whether overlay should be rendered, true by default
withinPortal
boolean
Determines whether component should be rendered inside Portal, true by default
xOffset
MarginLeft<string | number>
Left/right modal offset, 5vw by default
yOffset
MarginTop<string | number>
Top/bottom modal offset, 5vh by default
zIndex
number
z-index CSS property of root element, 200 by default

Modal component Styles API

NameStatic selectorDescription
root.mantine-Modal-rootRoot element
inner.mantine-Modal-innerElement used to center modal, has fixed position, takes entire screen
content.mantine-Modal-contentModal.Content root element
header.mantine-Modal-headerContains title and close button
overlay.mantine-Modal-overlayOverlay displayed under the Modal.Content
title.mantine-Modal-titleModal title (h2 tag), displayed in header
body.mantine-Modal-bodyModal body, displayed after header
close.mantine-Modal-closeClose button