Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
350 lines (270 sloc) 10.4 KB
path redirect_from
/docs/dialog/
/components/overlay/
/components/overlay/overlaycontainer/
/components/overlay/overlayhide/
/components/overlay/overlayshow/
/components/overlay/overlaytoggle/
/components/backdrop/

Dialog (Modal)

Dialog follows the WAI-ARIA Dialog (Modal) Pattern. It's rendered within a Portal by default, but it also has a non-modal state, which doesn't use portals.

Installation

npm install reakit

Learn more in Get started.

Usage

import { useDialogState, Dialog, DialogDisclosure } from "reakit/Dialog";

function Example() {
  const dialog = useDialogState();
  return (
    <>
      <DialogDisclosure {...dialog}>Open dialog</DialogDisclosure>
      <Dialog aria-label="Welcome" {...dialog}>
        Welcome to Reakit!
      </Dialog>
    </>
  );
}

Backdrop

import {
  useDialogState,
  Dialog,
  DialogDisclosure,
  DialogBackdrop
} from "reakit/Dialog";

function Example() {
  const dialog = useDialogState();
  return (
    <>
      <DialogDisclosure {...dialog}>Open dialog</DialogDisclosure>
      <DialogBackdrop {...dialog} />
      <Dialog aria-label="Welcome" {...dialog}>
        Welcome to Reakit!
      </Dialog>
    </>
  );
}

Non-modal dialogs

There's still no consensus on how non-modal dialogs should behave. Some discussions like w3c/aria-practices#599 and this deleted section about non-modal dialogs indicate that it's pretty much a dialog that provides a keyboard mechanism to move focus outside it while leaving it open.

Reakit doesn't strictly follow that. When Dialog has modal set to false:

  • It doesn't render within a Portal.
  • Focus is not trapped within the dialog.
  • Body scroll isn't disabled.

There's a few use cases for these conditions, like Popover and Menu.

import { useDialogState, Dialog, DialogDisclosure } from "reakit/Dialog";

function Example() {
  const dialog = useDialogState();
  return (
    <>
      <DialogDisclosure {...dialog}>Open dialog</DialogDisclosure>
      <Dialog
        aria-label="Welcome"
        modal={false}
        style={{ position: "static", transform: "none" }}
        {...dialog}
      >
        Focus is not trapped within me.
      </Dialog>
    </>
  );
}

If desirable, a non-modal dialog can also be rendered within a Portal. The hideOnClickOutside prop can be set to false so clicking and focusing outside doesn't close it.

import { useDialogState, Dialog, DialogDisclosure } from "reakit/Dialog";
import { Button } from "reakit/Button";
import { Portal } from "reakit/Portal";

function Example() {
  const dialog = useDialogState();
  return (
    <>
      <DialogDisclosure {...dialog}>Open chat</DialogDisclosure>
      <Portal>
        <Dialog
          aria-label="Welcome"
          modal={false}
          hideOnClickOutside={false}
          style={{
            transform: "none",
            top: "auto",
            left: "auto",
            bottom: 0,
            right: 16,
            width: 200,
            height: 300
          }}
          {...dialog}
        >
          <Button onClick={dialog.hide}>Close chat</Button>
        </Dialog>
      </Portal>
    </>
  );
}

Nested dialogs

Reakit supports multiple nested modal dialogs and non-modal dialogs. ESC closes only the currently focused one. If the closed dialog has other open dialogs within, they will all be closed.

import { useDialogState, Dialog, DialogDisclosure } from "reakit/Dialog";
import { Button } from "reakit/Button";

function Example() {
  const dialog1 = useDialogState();
  const dialog2 = useDialogState();
  return (
    <>
      <DialogDisclosure {...dialog1}>Open dialog</DialogDisclosure>
      <Dialog aria-label="Test" {...dialog1}>
        <p>
          Press <kbd>ESC</kbd> to close me.
        </p>
        <div style={{ display: "grid", gridGap: 16, gridAutoFlow: "column" }}>
          <Button onClick={dialog1.hide}>Close dialog</Button>
          <DialogDisclosure {...dialog2}>Open nested dialog</DialogDisclosure>
        </div>
        <Dialog aria-label="Nested" {...dialog2}>
          <Button onClick={dialog2.hide}>Close nested dialog</Button>
        </Dialog>
      </Dialog>
    </>
  );
}

Alert dialogs

A dialog can be turned into an alert dialog by just setting its role prop to alertdialog. See WAI-ARIA Alert and Message Dialogs Pattern.

import { useDialogState, Dialog, DialogDisclosure } from "reakit/Dialog";
import { Button } from "reakit/Button";

function Example() {
  const dialog = useDialogState();
  return (
    <>
      <DialogDisclosure {...dialog}>Discard</DialogDisclosure>
      <Dialog role="alertdialog" aria-label="Confirm discard" {...dialog}>
        <p>Are you sure you want to discard it?</p>
        <div style={{ display: "grid", gridGap: 16, gridAutoFlow: "column" }}>
          <Button onClick={dialog.hide}>Cancel</Button>
          <Button
            onClick={() => {
              alert("Discarded");
              dialog.hide();
            }}
          >
            Discard
          </Button>
        </div>
      </Dialog>
    </>
  );
}

Accessibility

  • Dialog has role dialog.
  • Dialog has aria-modal set to true unless the modal prop is set to false.
  • When Dialog opens, focus moves to an element inside the dialog.
  • Focus is trapped within the modal Dialog.
  • ESC closes Dialog unless hideOnEsc is set to false.
  • Clicking outside the Dialog closes it unless hideOnClickOutside is set to false.
  • Focusing outside the non-modal Dialog closes it unless hideOnClickOutside is set to false.
  • When Dialog closes, focus returns to its disclosure unless the closing action has been triggered by a click/focus on a tabbable element outside the Dialog. In this case, Dialog closes and this element remains with focus.
  • DialogDisclosure extends the accessibility features of HiddenDisclosure.

Learn more in Accessibility.

Composition

Learn more in Composition.

Props

useDialogState

  • visible boolean

    Whether it's visible or not.

Dialog

  • visible boolean

    Whether it's visible or not.

  • unstable_animated ⚠️ boolean | undefined

    If true, the hidden element attributes will be set in different timings to enable CSS transitions. This means that you can safely use the .hidden selector in the CSS to create transitions.

    • When it becomes visible, immediatelly remove the hidden attribute, then add the hidden class.
    • When it becomes hidden, immediatelly remove the hidden class, then add the hidden attribute.
  • hide () => void

    Changes the visible state to false

  • modal boolean | undefined

    Toggles Dialog's modal state.

    • Non-modal: preventBodyScroll doesn't work and focus is free.
    • Modal: preventBodyScroll is automatically enabled, focus is trapped within the dialog and the dialog is rendered within a Portal by default.
  • hideOnEsc boolean | undefined

    When enabled, user can hide the dialog by pressing Escape.

  • hideOnClickOutside boolean | undefined

    When enabled, user can hide the dialog by clicking outside it.

  • preventBodyScroll boolean | undefined

    When enabled, user can't scroll on body when the dialog is visible. This option doesn't work if the dialog isn't modal.

  • unstable_initialFocusRef ⚠️ RefObject<HTMLElement> | undefined

    The element that will be focused when the dialog shows. When not set, the first tabbable element within the dialog will be used.

  • unstable_finalFocusRef ⚠️ RefObject<HTMLElement> | undefined

    The element that will be focused when the dialog hides. When not set, the disclosure component will be used.

  • unstable_portal ⚠️ boolean | undefined

    Whether or not the dialog should be rendered within Portal. It's true by default if modal is true.

  • unstable_orphan ⚠️ boolean | undefined

    Whether or not the dialog should be a child of its parent. Opening a nested orphan dialog will close its parent dialog if hideOnClickOutside is set to true on the parent. It will be set to false if modal is false.

DialogBackdrop

  • visible boolean

    Whether it's visible or not.

  • unstable_animated ⚠️ boolean | undefined

    If true, the hidden element attributes will be set in different timings to enable CSS transitions. This means that you can safely use the .hidden selector in the CSS to create transitions.

    • When it becomes visible, immediatelly remove the hidden attribute, then add the hidden class.
    • When it becomes hidden, immediatelly remove the hidden class, then add the hidden attribute.

DialogDisclosure

  • disabled boolean | undefined

    Same as the HTML attribute.

  • focusable boolean | undefined

    When an element is disabled, it may still be focusable. It works similarly to readOnly on form elements. In this case, only aria-disabled will be set.

  • visible boolean

    Whether it's visible or not.

  • toggle () => void

    Toggles the visible state

You can’t perform that action at this time.