Ariakit
/
/
This site is under construction Subscribe to updates

Dialog with details & summary

Combining Dialog with the native details element in React so users can interact with it before JavaScript finishes loading.

Before you use this example

This is not the best way to make modal dialogs accessible without JavaScript. Consider using Dialog with React Router instead.

Try it by hard-refreshing this page. Then click on the Show modal button below before JavaScript finishes loading.

Show modal
import { useEffect, useRef } from "react";
import { Button } from "ariakit/button";
import {
  Dialog,
  DialogDismiss,
  DialogHeading,
  useDialogState,
} from "ariakit/dialog";
import "./style.css";

export default function Example() {
  const ref = useRef<HTMLDetailsElement>(null);
  const dialog = useDialogState();

  // Hydrate the dialog state. This is necessary because the user may have
  // opened the dialog before JavaScript has loaded.
  useEffect(() => dialog.setOpen(!!ref.current?.open), [dialog.setOpen]);

  return (
    <details
      ref={ref}
      open={dialog.mounted}
      onToggle={(event) => dialog.setOpen(event.currentTarget.open)}
    >
      <Button as="summary" className="button">
        Show modal
      </Button>
      <Dialog
        state={dialog}
        // We're setting the modal prop to true only when the dialog is open and
        // JavaScript is enabled. This means that the dialog will initially have
        // a non-modal state with no backdrop element, allowing users to
        // interact with the content behind. This is necessary because, before
        // JavaScript finishes loading, we can't automatically move focus to the
        // dialog.
        modal={dialog.mounted}
        hidden={false}
        className="dialog"
      >
        <DialogHeading className="heading">Success</DialogHeading>
        <p className="description">
          Your payment has been successfully processed. We have emailed your
          receipt.
        </p>
        <div>
          <DialogDismiss className="button">OK</DialogDismiss>
        </div>
      </Dialog>
    </details>
  );
}

Styling

Resetting the default styles

By default, browsers apply some default styles to the details element. We can reset them with the following CSS:

.button {
  appearance: none;
}

.button::marker,
.button::-webkit-details-marker {
  display: none;
}

Fixing the layout shift

Since we're changing between non-modal and modal states and, therefore, between non-portal and portal dialogs right after it's shown, there may be a layout shift if the browser has visible scrollbars.

To fix this, we can use the --scrollbar-width CSS variable:

.dialog {
  margin-left: calc(var(--scrollbar-width, 0) * -0.5);
}

Learn more in Styling.