🛖
Published on

Modal

Authors
  • avatar
    Name
    Zhifa Qiu
    Twitter

Showcase

Requirements

Create a modal (dialog) component that displays its content in an overlay window above the page.

High-level design

ComponentRole
ModalWrapper component
Modal.HeaderTop section of the modal, which renders title and close button
Modal.ContentMiddle section of the modal, which renders the main content
Modal.FooterBottom section of the modal
Modal.BackdropVisually separates the modal from the rest of the page
<Modal>
  <Modal.Backdrop />
  <Modal.Header>
    <Header />
  </Modal.Header>
  <Modal.Content>
    <Content />
  </Modal.Content>
  <Modal.Footer>
    <Footer />
  </Modal.Footer>
</Modal>

API

General props

PropTypeDescription
childrenReactNodeChild component
asElementTypeCustom DOM element
classNamestringcomponent style
PropTypeDescription
openbooleandetermine if a modal is open
onClosefunctioncallback when a modal is closed

Deep dive

Scroll lock

When a modal is displayed, disable background scrolling to prevent users from moving the page behind the dialog. This ensures focus stays on the modal and provides a consistent user experience.

useEffect(() => {
  const originalStyle = document.body.style.overflow

  if (open) {
    document.body.style.overflow = 'hidden'
  }

  return () => {
    document.body.style.overflow = originalStyle
  }
}, [open])

Click outside

Clicking outside the modal will close it.

const onClickOutsideHandler = useCallback(
  (e: Event) => {
    if (ref.current && !ref.current.contains(e.target as Node)) {
      onClose()
    }
  },
  [onClose]
)

useEffect(() => {
  if (!open) return

  document.addEventListener('mousedown', onClickOutsideHandler)
  document.addEventListener('touchstart', onClickOutsideHandler)

  return () => {
    document.removeEventListener('mousedown', onClickOutsideHandler)
    document.removeEventListener('touchstart', onClickOutsideHandler)
  }
}, [open, onClickOutsideHandler])

a11y

  • role = 'dialog' tells screen reader that this element is a dialog window

  • aria-modal='true'

    • Users must interact with it before returning to the rest of the page.
    • The page behind should be treated as unavailable until the
  • aria-labelledby tells which element's text should be used as the accessible name

  • aria-label gives the element an accessible name

  • aria-describedby tells screen reader which element provides a longer description for another element

Focus trap

To make sure users — especially those relying on keyboards or assistive tech — don’t accidentally move focus outside of the modal while it’s open.

Focus trap react

Animation

Modal animations can be implemented using an animation library and the compound-component pattern.