Dialog

A dialog is a dialog that focuses the user's attention exclusively on an information via a window that is overlaid on primary content.

Source@chakra-ui/dialog

Import#

import { Dialog } from '@chakra-ui/react'

Usage#

When the dialog opens:

  • focus is trapped within the dialog and set to the first tabbable element.
  • content behind a dialog dialog is inert, meaning that users cannot interact with it.
function BasicUsage() {
const { open, onOpen, onClose } = useDisclosure()
return (
<>
<Button onClick={onOpen}>Open Dialog</Button>
<Dialog.Root open={open} onClose={onClose}>
<Dialog.Backdrop />
<Dialog.Positioner>
<Dialog.Content>
<Dialog.Header>Dialog Title</Dialog.Header>
<Dialog.CloseTrigger />
<Dialog.Body>
<Lorem count={2} />
</Dialog.Body>
<Dialog.Footer>
<Button mr={3} onClick={onClose}>
Close
</Button>
<Button variant='ghost'>Secondary Action</Button>
</Dialog.Footer>
</Dialog.Content>
</Dialog.Positioner>
</Dialog.Root>
</>
)
}

Control Focus when Dialog closes#

When the dialog closes, it returns focus to the element that triggered it. Set finalFocusRef to change the element that should receive focus when the dialog closes.

function ReturnFocus() {
const { open, onOpen, onClose } = useDisclosure()
const finalRef = React.useRef(null)
return (
<>
<Box ref={finalRef} tabIndex={-1} aria-label='Focus moved to this box'>
Some other content that'll receive focus on close.
</Box>
<Button mt={4} onClick={onOpen}>
Open Dialog
</Button>
<Dialog.Root finalFocusRef={finalRef} open={open} onClose={onClose}>
<Dialog.Backdrop />
<Dialog.Positioner>
<Dialog.Content>
<Dialog.Header>Dialog Title</Dialog.Header>
<Dialog.CloseTrigger />
<Dialog.Body>
<Lorem count={2} />
</Dialog.Body>
<Dialog.Footer>
<Button mr={3} onClick={onClose}>
Close
</Button>
<Button variant='ghost'>Secondary Action</Button>
</Dialog.Footer>
</Dialog.Content>
</Dialog.Positioner>
</Dialog.Root>
</>
)
}

Block Scrolling when Dialog opens#

For accessibility, it is recommended to block scrolling on the main document behind the dialog. Chakra does this by default but you can set blockScrollOnMount to false to allow scrolling behind the dialog.

function BasicUsage() {
const { open, onOpen, onClose } = useDisclosure()
return (
<>
<Button onClick={onOpen}>Open Dialog</Button>
<Dialog.Root blockScrollOnMount={false} open={open} onClose={onClose}>
<Dialog.Backdrop />
<Dialog.Positioner>
<Dialog.Content>
<Dialog.Header>Dialog Title</Dialog.Header>
<Dialog.CloseTrigger />
<Dialog.Body>
<Text fontWeight='bold' mb='1rem'>
You can scroll the content behind the dialog
</Text>
<Lorem count={2} />
</Dialog.Body>
<Dialog.Footer>
<Button mr={3} onClick={onClose}>
Close
</Button>
<Button variant='ghost'>Secondary Action</Button>
</Dialog.Footer>
</Dialog.Content>
</Dialog.Positioner>
</Dialog.Root>
</>
)
}

Focus on specific element#

Chakra automatically sets focus on the first tabbable element in the dialog. However, there might be scenarios where you need to manually control where focus goes.

Chakra provides 2 props for this use case:

  • initialFocusRef: The ref of the component that receives focus when the dialog opens.
  • finalFocusRef: The ref of the component that receives focus when the dialog closes.

If you set finalFocusRef, internally we set returnFocusOnClose to false so it doesn't return focus to the element that triggered it.

function InitialFocus() {
const { open, onOpen, onClose } = useDisclosure()
const initialRef = React.useRef(null)
const finalRef = React.useRef(null)
return (
<>
<Button onClick={onOpen}>Open Dialog</Button>
<Button ml={4} ref={finalRef}>
I'll receive focus on close
</Button>
<Dialog
initialFocusRef={initialRef}
finalFocusRef={finalRef}
open={open}
onClose={onClose}
>
<Dialog.Backdrop />
<Dialog.Positioner>
<Dialog.Content>
<Dialog.Header>Create your account</Dialog.Header>
<Dialog.CloseTrigger />
<Dialog.Body pb={6}>
<Field.Root>
<Field.Label>First name</Field.Label>
<Input ref={initialRef} placeholder='First name' />
</Field.Root>
<Field.Root mt={4}>
<Field.Label>Last name</Field.Label>
<Input placeholder='Last name' />
</Field.Root>
</Dialog.Body>
<Dialog.Footer>
<Button mr={3}>Save</Button>
<Button onClick={onClose}>Cancel</Button>
</Dialog.Footer>
</Dialog.Content>
</Dialog.Positioner>
</Dialog>
</>
)
}

Close Dialog on Overlay Click#

By default, the dialog closes when you click its overlay. You can set closeOnOverlayClick to false if you want the dialog to stay visible.

function ManualClose() {
const { open, onOpen, onClose } = useDisclosure()
return (
<>
<Button onClick={onOpen}>Open Dialog</Button>
<Dialog closeOnOverlayClick={false} open={open} onClose={onClose}>
<Dialog.Backdrop />
<Dialog.Positioner>
<Dialog.Content>
<Dialog.Header>Create your account</Dialog.Header>
<Dialog.CloseTrigger />
<Dialog.Body pb={6}>
<Lorem count={2} />
</Dialog.Body>
<Dialog.Footer>
<Button mr={3}>Save</Button>
<Button onClick={onClose}>Cancel</Button>
</Dialog.Footer>
</Dialog.Content>
</Dialog.Positioner>
</Dialog>
</>
)
}

Make dialog vertically centered#

By default the dialog has a vertical offset of 3.75rem which you can change by passing top to the Dialog.Content.

If you need to vertically center the dialog, pass the centered prop.

If the content within the dialog overflows beyond the viewport, don't use this prop. Try setting the overflow behavior instead.

function VerticallyCenter() {
const { open, onOpen, onClose } = useDisclosure()
return (
<>
<Button onClick={onOpen}>Trigger dialog</Button>
<Dialog onClose={onClose} open={open} centered>
<Dialog.Backdrop />
<Dialog.Positioner>
<Dialog.Content>
<Dialog.Header>Dialog Title</Dialog.Header>
<Dialog.CloseTrigger />
<Dialog.Body>
<Lorem count={2} />
</Dialog.Body>
<Dialog.Footer>
<Button onClick={onClose}>Close</Button>
</Dialog.Footer>
</Dialog.Content>
</Dialog.Positioner>
</Dialog>
</>
)
}

Changing the transition#

The Dialog comes with a scale transition by default but you can change it by passing a motionPreset prop, and setting its value to either slideInBottom, slideInRight, scale or none.

function TransitionExample() {
const { open, onOpen, onClose } = useDisclosure()
return (
<>
<Button onClick={onOpen}>Open Dialog</Button>
<Dialog
centered
onClose={onClose}
open={open}
motionPreset='slideInBottom'
>
<Dialog.Backdrop />
<Dialog.Positioner>
<Dialog.Content>
<Dialog.Header>Dialog Title</Dialog.Header>
<Dialog.CloseTrigger />
<Dialog.Body>
<Lorem count={2} />
</Dialog.Body>
<Dialog.Footer>
<Button mr={3} onClick={onClose}>
Close
</Button>
<Button variant='ghost'>Secondary Action</Button>
</Dialog.Footer>
</Dialog.Content>
</Dialog.Positioner>
</Dialog>
</>
)
}

Changing to alert dialog#

Set the role prop to alertdialog to change the dialog to an alert dialog.

Dialog overflow behavior#

If the content within the dialog overflows beyond the viewport, you can use the scrollBehavior to control how scrolling should happen.

  • If set to inside, scroll only occurs within the Dialog.Body.
  • If set to outside, the entire Dialog.Content will scroll within the viewport.
function ScrollingExample() {
const { open, onOpen, onClose } = useDisclosure()
const [scrollBehavior, setScrollBehavior] = React.useState('inside')
const btnRef = React.useRef(null)
return (
<>
<RadioGroup.Root value={scrollBehavior} onChange={setScrollBehavior}>
<Stack direction='row'>
<RadioGroup.Item value='inside'>inside</RadioGroup.Item>
<RadioGroup.Item value='outside'>outside</RadioGroup.Item>
</Stack>
</RadioGroup.Root>
<Button mt={3} ref={btnRef} onClick={onOpen}>
Trigger dialog
</Button>
<Dialog
onClose={onClose}
finalFocusRef={btnRef}
open={open}
scrollBehavior={scrollBehavior}
>
<Dialog.Backdrop />
<Dialog.Positioner>
<Dialog.Content>
<Dialog.Header>Dialog Title</Dialog.Header>
<Dialog.CloseTrigger />
<Dialog.Body>
<Lorem count={15} />
</Dialog.Body>
<Dialog.Footer>
<Button onClick={onClose}>Close</Button>
</Dialog.Footer>
</Dialog.Content>
</Dialog.Positioner>
</Dialog>
</>
)
}

Dialog Sizes#

Pass the size prop if you need to adjust the size of the dialog. Values can be xs, sm, md, lg, xl, or full.

function SizeExample() {
const { open, onOpen, onClose } = useDisclosure()
const [size, setSize] = React.useState('md')
const handleSizeClick = (newSize) => {
setSize(newSize)
onOpen()
}
const sizes = ['xs', 'sm', 'md', 'lg', 'xl', 'full']
return (
<>
{sizes.map((size) => (
<Button
onClick={() => handleSizeClick(size)}
key={size}
m={4}
>{`Open ${size} Dialog`}</Button>
))}
<Dialog onClose={onClose} size={size} open={open}>
<Dialog.Backdrop />
<Dialog.Positioner>
<Dialog.Content>
<Dialog.Header>Dialog Title</Dialog.Header>
<Dialog.CloseTrigger />
<Dialog.Body>
<Lorem count={2} />
</Dialog.Body>
<Dialog.Footer>
<Button onClick={onClose}>Close</Button>
</Dialog.Footer>
</Dialog.Content>
</Dialog.Positioner>
</Dialog>
</>
)
}

Making other elements Inert#

When the dialog is open, it is rendered within a portal and all its siblings have aria-hidden set to true so the only thing screen readers see is the dialog. To disable this behavior, set useInert to false.

Prevent focus trapping#

By default the dialog, alert dialog and drawer locks the focus inside them. Normally this is what you want to maintain accessibility standards.

While we strongly discourage this use case due to the accessibility impacts, there are certain situations where you might not want the dialog to trap focus.

To prevent focus trapping, pass trapFocus and set its value to false.

Styling the backdrop#

The backdrop's background by default is set to blackAlpha.600, but if you want to achieve a different style you can also use the backdrop style props, like backdropBlur, backdropBrightness, backdropContrast, backdropHueRotate, backdropInvert, and backdropSaturate. To use these style props, you'd have to set the backdropFilter prop to auto.

Please be aware that not every browser supports the backdrop-filter CSS property, the example below included.

The filter property will have no effect on the background because the Dialog is rendered within a Portal. This means you can only style components within the Dialog by using this property.

function BackdropExample() {
const OverlayOne = () => (
<Dialog.Backdrop
bg='blackAlpha.300'
backdropFilter='blur(10px) hue-rotate(90deg)'
/>
)
const OverlayTwo = () => (
<Dialog.Backdrop
bg='none'
backdropFilter='auto'
backdropInvert='80%'
backdropBlur='2px'
/>
)
const { open, onOpen, onClose } = useDisclosure()
const [overlay, setOverlay] = React.useState(<OverlayOne />)
return (
<>
<Button
onClick={() => {
setOverlay(<OverlayOne />)
onOpen()
}}
>
Use Overlay one
</Button>
<Button
ml='4'
onClick={() => {
setOverlay(<OverlayTwo />)
onOpen()
}}
>
Use Overlay two
</Button>
<Dialog centered open={open} onClose={onClose}>
{overlay}
<Dialog.Positioner>
<Dialog.Content>
<Dialog.Header>Dialog Title</Dialog.Header>
<Dialog.CloseTrigger />
<Dialog.Body>
<Text>Custom backdrop filters!</Text>
</Dialog.Body>
<Dialog.Footer>
<Button onClick={onClose}>Close</Button>
</Dialog.Footer>
</Dialog.Content>
</Dialog.Positioner>
</Dialog>
</>
)
}

Accessibility#

Keyboard and Focus Management#

  • When the dialog opens, focus is trapped within it.
  • When the dialog opens, focus is automatically set to the first enabled element, or the element from initialFocusRef.
  • When the dialog closes, focus returns to the element that was focused before the dialog activated, or the element from finalFocusRef.
  • Clicking on the overlay closes the Dialog.
  • Pressing Esc closes the Dialog.
  • Scrolling is blocked on the elements behind the dialog.
  • The dialog is rendered in a portal attached to the end of document.body to break it out of the source order and make it easy to add aria-hidden to its siblings.

ARIA#

  • The Dialog.Content has aria-dialog set to true.
  • The Dialog.Content has aria-labelledby set to the id of the Dialog.Header.
  • The Dialog.Content has aria-describedby set to the id of the Dialog.Body.
Edit this page on GitHub

Proudly made inNigeria by Segun Adebayo

Deployed by â–² Vercel