useDialog

Provides the behavior and accessibility implementation for a dialog component. A dialog is an overlay shown above other content in an application.

installyarn add react-aria
version3.19.0
usageimport {useDialog} from 'react-aria'

API#


useDialog( (props: AriaDialogProps, , ref: RefObject<FocusableElement> )): DialogAria

Features#


The HTML <dialog> element can be used to build dialogs. However, it is not yet widely supported across browsers, and building fully accessible custom dialogs from scratch is very difficult and error prone. useDialog, combined with useModal, helps achieve accessible dialogs that can be styled as needed.

  • Exposed to assistive technology as a dialog or alertdialog with ARIA
  • Handles labelling the dialog by its title element
  • Handles focusing the dialog on mount, unless a child element is already focused
  • Contains focus inside the dialog when combined with <FocusScope>
  • Hides content behind the dialog from screen readers when combined with useModal
  • Prevents scrolling the page behind the dialog when combined with usePreventScroll
  • Handles closing the dialog when interacting outside and pressing the Escape key, when combined with useOverlay

Anatomy#


CancelEnableEnable smart filters?Smart filters are nondestructive and will preserve your original images.TitleDialog

A dialog consists of a container element and an optional title. useDialog handles exposing this to assistive technology using ARIA. It can be combined with useModal, usePreventScroll, and useOverlay as needed to create modal dialogs, popovers, and other types of overlays.

useDialog returns props that you should spread onto the appropriate element:

NameTypeDescription
dialogPropsDOMAttributesProps for the dialog container element.
titlePropsDOMAttributesProps for the dialog title element.

If a dialog does not have a visible title element, an aria-label or aria-labelledby prop must be passed instead to identify the element to assistive technology.

Example#


This example shows how to build a typical modal dialog. It has a partially transparent backdrop above the rest of the page, prevents scrolling the body using usePreventScroll, and hides content outside the dialog with useModal.

The modal can be closed by clicking or interacting outside the dialog if the isDismissable prop is set to true, or by pressing the Escape key. This is handled by useOverlay.

Focus is contained within the dialog while it is open using a <FocusScope>. In addition, the first focusable element is automatically focused when the dialog opens, and focus is restored back to the button that opened it when it closes.

The application is contained in an OverlayProvider, which is used to hide the content from screen readers with aria-hidden while a modal is open. In addition, each modal must be contained in an OverlayContainer, which uses a React Portal to render the modal at the end of the document body. If a nested modal is opened, then the first modal will also be set to aria-hidden, so that only the top-most modal is accessible.

Note: useModal only hides content within parent OverlayProvider components. However, if you have additional content in your application outside any OverlayProvider, then you should use the @react-aria/aria-modal-polyfill package to ensure that this content is hidden while modals are open as well. See the watchModals docs for more information.

import {FocusScope, OverlayContainer, OverlayProvider, useDialog, useModal, useOverlay, usePreventScroll} from 'react-aria';

import {useOverlayTriggerState} from 'react-stately';

// Reuse the Button from your component library. See below for details.
import {Button} from 'your-component-library';

function ModalDialog(props) {
  let { title, children } = props;

  // Handle interacting outside the dialog and pressing
  // the Escape key to close the modal.
  let ref = React.useRef();
  let { overlayProps, underlayProps } = useOverlay(props, ref);

  // Prevent scrolling while the modal is open, and hide content
  // outside the modal from screen readers.
  usePreventScroll();
  let { modalProps } = useModal();

  // Get props for the dialog and its title
  let { dialogProps, titleProps } = useDialog(props, ref);

  return (
    <div
      style={{
        position: 'fixed',
        zIndex: 100,
        top: 0,
        left: 0,
        bottom: 0,
        right: 0,
        background: 'rgba(0, 0, 0, 0.5)',
        display: 'flex',
        alignItems: 'center',
        justifyContent: 'center'
      }}
      {...underlayProps}
    >
      <FocusScope contain restoreFocus autoFocus>
        <div
          {...overlayProps}
          {...dialogProps}
          {...modalProps}
          ref={ref}
          style={{
            background: 'white',
            color: 'black',
            padding: 30
          }}
        >
          <h3
            {...titleProps}
            style={{ marginTop: 0 }}
          >
            {title}
          </h3>
          {children}
        </div>
      </FocusScope>
    </div>
  );
}

function Example() {
  let state = useOverlayTriggerState({});

  return (
    <>
      <Button onPress={state.open}>Open Dialog</Button>
      {state.isOpen &&
        (
          <OverlayContainer>
            <ModalDialog
              title="Enter your name"
              isOpen
              onClose={state.close}
              isDismissable
            >
              <form style={{ display: 'flex', flexDirection: 'column' }}>
                <label htmlFor="first-name">First Name:</label>
                <input id="first-name" />
                <label htmlFor="last-name">Last Name:</label>
                <input id="last-name" />
                <Button
                  onPress={state.close}
                  style={{ marginTop: 10 }}
                >
                  Submit
                </Button>
              </form>
            </ModalDialog>
          </OverlayContainer>
        )}
    </>
  );
}

// Application must be wrapped in an OverlayProvider so that it can be
// hidden from screen readers when a modal opens.
<OverlayProvider>
  <Example />
</OverlayProvider>
import {
  FocusScope,
  OverlayContainer,
  OverlayProvider,
  useDialog,
  useModal,
  useOverlay,
  usePreventScroll
} from 'react-aria';

import {useOverlayTriggerState} from 'react-stately';

// Reuse the Button from your component library. See below for details.
import {Button} from 'your-component-library';

function ModalDialog(props) {
  let { title, children } = props;

  // Handle interacting outside the dialog and pressing
  // the Escape key to close the modal.
  let ref = React.useRef();
  let { overlayProps, underlayProps } = useOverlay(
    props,
    ref
  );

  // Prevent scrolling while the modal is open, and hide content
  // outside the modal from screen readers.
  usePreventScroll();
  let { modalProps } = useModal();

  // Get props for the dialog and its title
  let { dialogProps, titleProps } = useDialog(props, ref);

  return (
    <div
      style={{
        position: 'fixed',
        zIndex: 100,
        top: 0,
        left: 0,
        bottom: 0,
        right: 0,
        background: 'rgba(0, 0, 0, 0.5)',
        display: 'flex',
        alignItems: 'center',
        justifyContent: 'center'
      }}
      {...underlayProps}
    >
      <FocusScope contain restoreFocus autoFocus>
        <div
          {...overlayProps}
          {...dialogProps}
          {...modalProps}
          ref={ref}
          style={{
            background: 'white',
            color: 'black',
            padding: 30
          }}
        >
          <h3
            {...titleProps}
            style={{ marginTop: 0 }}
          >
            {title}
          </h3>
          {children}
        </div>
      </FocusScope>
    </div>
  );
}

function Example() {
  let state = useOverlayTriggerState({});

  return (
    <>
      <Button onPress={state.open}>Open Dialog</Button>
      {state.isOpen &&
        (
          <OverlayContainer>
            <ModalDialog
              title="Enter your name"
              isOpen
              onClose={state.close}
              isDismissable
            >
              <form
                style={{
                  display: 'flex',
                  flexDirection: 'column'
                }}
              >
                <label htmlFor="first-name">
                  First Name:
                </label>
                <input id="first-name" />
                <label htmlFor="last-name">
                  Last Name:
                </label>
                <input id="last-name" />
                <Button
                  onPress={state.close}
                  style={{ marginTop: 10 }}
                >
                  Submit
                </Button>
              </form>
            </ModalDialog>
          </OverlayContainer>
        )}
    </>
  );
}

// Application must be wrapped in an OverlayProvider so that it can be
// hidden from screen readers when a modal opens.
<OverlayProvider>
  <Example />
</OverlayProvider>
import {
  FocusScope,
  OverlayContainer,
  OverlayProvider,
  useDialog,
  useModal,
  useOverlay,
  usePreventScroll
} from 'react-aria';

import {useOverlayTriggerState} from 'react-stately';

// Reuse the Button from your component library. See below for details.
import {Button} from 'your-component-library';

function ModalDialog(
  props
) {
  let {
    title,
    children
  } = props;

  // Handle interacting outside the dialog and pressing
  // the Escape key to close the modal.
  let ref = React
    .useRef();
  let {
    overlayProps,
    underlayProps
  } = useOverlay(
    props,
    ref
  );

  // Prevent scrolling while the modal is open, and hide content
  // outside the modal from screen readers.
  usePreventScroll();
  let { modalProps } =
    useModal();

  // Get props for the dialog and its title
  let {
    dialogProps,
    titleProps
  } = useDialog(
    props,
    ref
  );

  return (
    <div
      style={{
        position:
          'fixed',
        zIndex: 100,
        top: 0,
        left: 0,
        bottom: 0,
        right: 0,
        background:
          'rgba(0, 0, 0, 0.5)',
        display: 'flex',
        alignItems:
          'center',
        justifyContent:
          'center'
      }}
      {...underlayProps}
    >
      <FocusScope
        contain
        restoreFocus
        autoFocus
      >
        <div
          {...overlayProps}
          {...dialogProps}
          {...modalProps}
          ref={ref}
          style={{
            background:
              'white',
            color:
              'black',
            padding: 30
          }}
        >
          <h3
            {...titleProps}
            style={{
              marginTop:
                0
            }}
          >
            {title}
          </h3>
          {children}
        </div>
      </FocusScope>
    </div>
  );
}

function Example() {
  let state =
    useOverlayTriggerState(
      {}
    );

  return (
    <>
      <Button
        onPress={state
          .open}
      >
        Open Dialog
      </Button>
      {state.isOpen &&
        (
          <OverlayContainer>
            <ModalDialog
              title="Enter your name"
              isOpen
              onClose={state
                .close}
              isDismissable
            >
              <form
                style={{
                  display:
                    'flex',
                  flexDirection:
                    'column'
                }}
              >
                <label htmlFor="first-name">
                  First
                  Name:
                </label>
                <input id="first-name" />
                <label htmlFor="last-name">
                  Last
                  Name:
                </label>
                <input id="last-name" />
                <Button
                  onPress={state
                    .close}
                  style={{
                    marginTop:
                      10
                  }}
                >
                  Submit
                </Button>
              </form>
            </ModalDialog>
          </OverlayContainer>
        )}
    </>
  );
}

// Application must be wrapped in an OverlayProvider so that it can be
// hidden from screen readers when a modal opens.
<OverlayProvider>
  <Example />
</OverlayProvider>

Button#

The Button component is used in the above example to open and close the dialog. It is built using the useButton hook, and can be shared with many other components.

Show code
import {useButton} from 'react-aria';

function Button(props) {
  let ref = React.useRef();
  let { buttonProps } = useButton(props, ref);
  return (
    <button {...buttonProps} ref={ref} style={props.style}>
      {props.children}
    </button>
  );
}
import {useButton} from 'react-aria';

function Button(props) {
  let ref = React.useRef();
  let { buttonProps } = useButton(props, ref);
  return (
    <button {...buttonProps} ref={ref} style={props.style}>
      {props.children}
    </button>
  );
}
import {useButton} from 'react-aria';

function Button(props) {
  let ref = React
    .useRef();
  let { buttonProps } =
    useButton(
      props,
      ref
    );
  return (
    <button
      {...buttonProps}
      ref={ref}
      style={props.style}
    >
      {props.children}
    </button>
  );
}

Styled examples#


Tailwind CSS
An animated alert dialog using Tailwind and react-transition-group.