Modal

A modal is an overlay element which blocks interaction with elements outside it.

installyarn add react-aria-components
version1.4.1
usageimport {Modal} from 'react-aria-components'

Example#


import {Button, Dialog, DialogTrigger, Heading, Input, Label, Modal, TextField} from 'react-aria-components';

<DialogTrigger>
  <Button>Sign up…</Button>
  <Modal>
    <Dialog>
      {({ close }) => (
        <form>
          <Heading slot="title">Sign up</Heading>
          <TextField autoFocus>
            <Label>First Name:</Label>
            <Input />
          </TextField>
          <TextField>
            <Label>Last Name:</Label>
            <Input />
          </TextField>
          <Button onPress={close}>
            Submit
          </Button>
        </form>
      )}
    </Dialog>
  </Modal>
</DialogTrigger>
import {
  Button,
  Dialog,
  DialogTrigger,
  Heading,
  Input,
  Label,
  Modal,
  TextField
} from 'react-aria-components';

<DialogTrigger>
  <Button>Sign up…</Button>
  <Modal>
    <Dialog>
      {({ close }) => (
        <form>
          <Heading slot="title">Sign up</Heading>
          <TextField autoFocus>
            <Label>First Name:</Label>
            <Input />
          </TextField>
          <TextField>
            <Label>Last Name:</Label>
            <Input />
          </TextField>
          <Button onPress={close}>
            Submit
          </Button>
        </form>
      )}
    </Dialog>
  </Modal>
</DialogTrigger>
import {
  Button,
  Dialog,
  DialogTrigger,
  Heading,
  Input,
  Label,
  Modal,
  TextField
} from 'react-aria-components';

<DialogTrigger>
  <Button>
    Sign up…
  </Button>
  <Modal>
    <Dialog>
      {({ close }) => (
        <form>
          <Heading slot="title">
            Sign up
          </Heading>
          <TextField
            autoFocus
          >
            <Label>
              First
              Name:
            </Label>
            <Input />
          </TextField>
          <TextField>
            <Label>
              Last
              Name:
            </Label>
            <Input />
          </TextField>
          <Button
            onPress={close}
          >
            Submit
          </Button>
        </form>
      )}
    </Dialog>
  </Modal>
</DialogTrigger>
Show CSS
@import "@react-aria/example-theme";

.react-aria-ModalOverlay {
  position: fixed;
  top: 0;
  left: 0;
  width: 100vw;
  height: var(--visual-viewport-height);
  background: rgba(0 0 0 / .5);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 100;

  &[data-entering] {
    animation: modal-fade 200ms;
  }

  &[data-exiting] {
    animation: modal-fade 150ms reverse ease-in;
  }
}

.react-aria-Modal {
  box-shadow: 0 8px 20px rgba(0 0 0 / 0.1);
  border-radius: 6px;
  background: var(--overlay-background);
  color: var(--text-color);
  border: 1px solid var(--gray-400);
  outline: none;
  max-width: 300px;

  &[data-entering] {
    animation: modal-zoom 300ms cubic-bezier(0.175, 0.885, 0.32, 1.275);
  }

  .react-aria-TextField {
    margin-bottom: 8px;
  }
}

@keyframes modal-fade {
  from {
    opacity: 0;
  }

  to {
    opacity: 1;
  }
}

@keyframes modal-zoom {
  from {
    transform: scale(0.8);
  }

  to {
    transform: scale(1);
  }
}
@import "@react-aria/example-theme";

.react-aria-ModalOverlay {
  position: fixed;
  top: 0;
  left: 0;
  width: 100vw;
  height: var(--visual-viewport-height);
  background: rgba(0 0 0 / .5);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 100;

  &[data-entering] {
    animation: modal-fade 200ms;
  }

  &[data-exiting] {
    animation: modal-fade 150ms reverse ease-in;
  }
}

.react-aria-Modal {
  box-shadow: 0 8px 20px rgba(0 0 0 / 0.1);
  border-radius: 6px;
  background: var(--overlay-background);
  color: var(--text-color);
  border: 1px solid var(--gray-400);
  outline: none;
  max-width: 300px;

  &[data-entering] {
    animation: modal-zoom 300ms cubic-bezier(0.175, 0.885, 0.32, 1.275);
  }

  .react-aria-TextField {
    margin-bottom: 8px;
  }
}

@keyframes modal-fade {
  from {
    opacity: 0;
  }

  to {
    opacity: 1;
  }
}

@keyframes modal-zoom {
  from {
    transform: scale(0.8);
  }

  to {
    transform: scale(1);
  }
}
@import "@react-aria/example-theme";

.react-aria-ModalOverlay {
  position: fixed;
  top: 0;
  left: 0;
  width: 100vw;
  height: var(--visual-viewport-height);
  background: rgba(0 0 0 / .5);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 100;

  &[data-entering] {
    animation: modal-fade 200ms;
  }

  &[data-exiting] {
    animation: modal-fade 150ms reverse ease-in;
  }
}

.react-aria-Modal {
  box-shadow: 0 8px 20px rgba(0 0 0 / 0.1);
  border-radius: 6px;
  background: var(--overlay-background);
  color: var(--text-color);
  border: 1px solid var(--gray-400);
  outline: none;
  max-width: 300px;

  &[data-entering] {
    animation: modal-zoom 300ms cubic-bezier(0.175, 0.885, 0.32, 1.275);
  }

  .react-aria-TextField {
    margin-bottom: 8px;
  }
}

@keyframes modal-fade {
  from {
    opacity: 0;
  }

  to {
    opacity: 1;
  }
}

@keyframes modal-zoom {
  from {
    transform: scale(0.8);
  }

  to {
    transform: scale(1);
  }
}

Features#


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

  • Styleable – States for entry and exit animations are included for easy styling. Both the underlay and overlay elements can be customized.
  • Accessible – Content outside the model is hidden from assistive technologies while it is open. The modal optionally closes when interacting outside, or pressing the Escape key.
  • Focus management – Focus is moved into the modal on mount, and restored to the trigger element on unmount. While open, focus is contained within the modal, preventing the user from tabbing outside.
  • Scroll locking – Scrolling the page behind the modal is prevented while it is open, including in mobile browsers.

Note: Modal only provides the overlay itself. It should be combined with Dialog to create fully accessible modal dialogs. Other overlays such as menus may also be placed in a modal overlay.

Anatomy#


UnderlayModal

A modal consists of an overlay container element, and an underlay. The overlay may contain a Dialog, or another element such as a Menu or ListBox when used within a component such as a Select or ComboBox. The underlay is typically a partially transparent element that covers the rest of the screen behind the overlay, and prevents the user from interacting with the elements behind it.

import {Modal, ModalOverlay} from 'react-aria-components';

<ModalOverlay>
  <Modal />
</ModalOverlay>
import {Modal, ModalOverlay} from 'react-aria-components';

<ModalOverlay>
  <Modal />
</ModalOverlay>
import {
  Modal,
  ModalOverlay
} from 'react-aria-components';

<ModalOverlay>
  <Modal />
</ModalOverlay>

Examples#


Destructive Alert Dialog
An animated confirmation dialog, styled with Tailwind CSS.
Gesture Driven Modal Sheet
An iOS-style gesture driven modal sheet built with Framer Motion.

Starter kits#


To help kick-start your project, we offer starter kits that include example implementations of all React Aria components with various styling solutions. All components are fully styled, including support for dark mode, high contrast mode, and all UI states. Each starter comes with a pre-configured Storybook that you can experiment with, or use as a starting point for your own component library.

Vanilla CSS
Download ZIP
Preview
Tailwind CSS
Download ZIP
Preview

Interactions#


Dismissable#

If your modal doesn't require the user to make a confirmation, you can set isDismissable on the Modal. This allows the user to click outside to close the dialog.

<DialogTrigger>
  <Button>Open dialog</Button>
  <Modal isDismissable>
    <Dialog>
      <Heading slot="title">Notice</Heading>
      <p>Click outside to close this dialog.</p>
    </Dialog>
  </Modal>
</DialogTrigger>
<DialogTrigger>
  <Button>Open dialog</Button>
  <Modal isDismissable>
    <Dialog>
      <Heading slot="title">Notice</Heading>
      <p>Click outside to close this dialog.</p>
    </Dialog>
  </Modal>
</DialogTrigger>
<DialogTrigger>
  <Button>
    Open dialog
  </Button>
  <Modal isDismissable>
    <Dialog>
      <Heading slot="title">
        Notice
      </Heading>
      <p>
        Click outside
        to close this
        dialog.
      </p>
    </Dialog>
  </Modal>
</DialogTrigger>

Keyboard dismiss disabled#

By default, modals can be closed by pressing the Escape key. This can be disabled with the isKeyboardDismissDisabled prop.

<DialogTrigger>
  <Button>Open dialog</Button>
  <Modal isKeyboardDismissDisabled>
    <Dialog>
      {({close}) => <>
        <Heading slot="title">Notice</Heading>
        <p>You must close this dialog using the button below.</p>
        <Button onPress={close}>Close</Button>
      </>}
    </Dialog>
  </Modal>
</DialogTrigger>
<DialogTrigger>
  <Button>Open dialog</Button>
  <Modal isKeyboardDismissDisabled>
    <Dialog>
      {({ close }) => (
        <>
          <Heading slot="title">Notice</Heading>
          <p>
            You must close this dialog using the button
            below.
          </p>
          <Button onPress={close}>Close</Button>
        </>
      )}
    </Dialog>
  </Modal>
</DialogTrigger>
<DialogTrigger>
  <Button>
    Open dialog
  </Button>
  <Modal
    isKeyboardDismissDisabled
  >
    <Dialog>
      {({ close }) => (
        <>
          <Heading slot="title">
            Notice
          </Heading>
          <p>
            You must
            close this
            dialog
            using the
            button
            below.
          </p>
          <Button
            onPress={close}
          >
            Close
          </Button>
        </>
      )}
    </Dialog>
  </Modal>
</DialogTrigger>

Custom overlay#


ModalOverlay can be used to customize the backdrop rendered behind a Modal. Together with support for custom entry and exit animations, you can build other types of overlays beyond traditional modal dialogs such as trays or drawers.

import {ModalOverlay} from 'react-aria-components';

<DialogTrigger>
  <Button>Open modal</Button>
  <ModalOverlay className="my-overlay">
    <Modal className="my-modal">
      <Dialog>
        {({close}) => <>
          <Heading slot="title">Notice</Heading>
          <p>This is a modal with a custom modal overlay.</p>
          <Button onPress={close}>Close</Button>
        </>}
      </Dialog>
    </Modal>
  </ModalOverlay>
</DialogTrigger>
import {ModalOverlay} from 'react-aria-components';

<DialogTrigger>
  <Button>Open modal</Button>
  <ModalOverlay className="my-overlay">
    <Modal className="my-modal">
      <Dialog>
        {({ close }) => (
          <>
            <Heading slot="title">Notice</Heading>
            <p>
              This is a modal with a custom modal overlay.
            </p>
            <Button onPress={close}>Close</Button>
          </>
        )}
      </Dialog>
    </Modal>
  </ModalOverlay>
</DialogTrigger>
import {ModalOverlay} from 'react-aria-components';

<DialogTrigger>
  <Button>
    Open modal
  </Button>
  <ModalOverlay className="my-overlay">
    <Modal className="my-modal">
      <Dialog>
        {(
          { close }
        ) => (
          <>
            <Heading slot="title">
              Notice
            </Heading>
            <p>
              This is a
              modal
              with a
              custom
              modal
              overlay.
            </p>
            <Button
              onPress={close}
            >
              Close
            </Button>
          </>
        )}
      </Dialog>
    </Modal>
  </ModalOverlay>
</DialogTrigger>
Show CSS
.my-overlay {
  position: fixed;
  inset: 0;
  background: rgba(45 0 0 / .3);
  backdrop-filter: blur(10px);

  &[data-entering] {
    animation: mymodal-blur 300ms;
  }

  &[data-exiting] {
    animation: mymodal-blur 300ms reverse ease-in;
  }
}

.my-modal {
  position: fixed;
  top: 0;
  bottom: 0;
  right: 0;
  width: 300px;
  background: var(--overlay-background);
  outline: none;
  border-left: 1px solid var(--border-color);
  box-shadow: -8px 0 20px rgba(0 0 0 / 0.1);

  &[data-entering] {
    animation: mymodal-slide 300ms;
  }

  &[data-exiting] {
    animation: mymodal-slide 300ms reverse ease-in;
  }
}

@keyframes mymodal-blur {
  from {
    background: rgba(45 0 0 / 0);
    backdrop-filter: blur(0);
  }

  to {
    background: rgba(45 0 0 / .3);
    backdrop-filter: blur(10px);
  }
}

@keyframes mymodal-slide {
  from {
    transform: translateX(100%);
  }

  to {
    transform: translateX(0);
  }
}
.my-overlay {
  position: fixed;
  inset: 0;
  background: rgba(45 0 0 / .3);
  backdrop-filter: blur(10px);

  &[data-entering] {
    animation: mymodal-blur 300ms;
  }

  &[data-exiting] {
    animation: mymodal-blur 300ms reverse ease-in;
  }
}

.my-modal {
  position: fixed;
  top: 0;
  bottom: 0;
  right: 0;
  width: 300px;
  background: var(--overlay-background);
  outline: none;
  border-left: 1px solid var(--border-color);
  box-shadow: -8px 0 20px rgba(0 0 0 / 0.1);

  &[data-entering] {
    animation: mymodal-slide 300ms;
  }

  &[data-exiting] {
    animation: mymodal-slide 300ms reverse ease-in;
  }
}

@keyframes mymodal-blur {
  from {
    background: rgba(45 0 0 / 0);
    backdrop-filter: blur(0);
  }

  to {
    background: rgba(45 0 0 / .3);
    backdrop-filter: blur(10px);
  }
}

@keyframes mymodal-slide {
  from {
    transform: translateX(100%);
  }

  to {
    transform: translateX(0);
  }
}
.my-overlay {
  position: fixed;
  inset: 0;
  background: rgba(45 0 0 / .3);
  backdrop-filter: blur(10px);

  &[data-entering] {
    animation: mymodal-blur 300ms;
  }

  &[data-exiting] {
    animation: mymodal-blur 300ms reverse ease-in;
  }
}

.my-modal {
  position: fixed;
  top: 0;
  bottom: 0;
  right: 0;
  width: 300px;
  background: var(--overlay-background);
  outline: none;
  border-left: 1px solid var(--border-color);
  box-shadow: -8px 0 20px rgba(0 0 0 / 0.1);

  &[data-entering] {
    animation: mymodal-slide 300ms;
  }

  &[data-exiting] {
    animation: mymodal-slide 300ms reverse ease-in;
  }
}

@keyframes mymodal-blur {
  from {
    background: rgba(45 0 0 / 0);
    backdrop-filter: blur(0);
  }

  to {
    background: rgba(45 0 0 / .3);
    backdrop-filter: blur(10px);
  }
}

@keyframes mymodal-slide {
  from {
    transform: translateX(100%);
  }

  to {
    transform: translateX(0);
  }
}

Controlled open state#


The above examples have shown Modal used within a <DialogTrigger>, which handles opening the modal when a button is clicked. This is convenient, but there are cases where you want to show a modal programmatically rather than as a result of a user action, or render the <Modal> in a different part of the JSX tree.

To do this, you can manage the modal's isOpen state yourself and provide it as a prop to the <Modal> element. The onOpenChange prop will be called when the user closes the modal, and should be used to update your state.

function Example() {
  let [isOpen, setOpen] = React.useState(false);

  return (
    <>
      <Button onPress={() => setOpen(true)}>Open dialog</Button>
      <Modal isDismissable isOpen={isOpen} onOpenChange={setOpen}>
        <Dialog>
          <Heading slot="title">Notice</Heading>
          <p>Click outside to close this dialog.</p>
        </Dialog>
      </Modal>
    </>
  );
}
function Example() {
  let [isOpen, setOpen] = React.useState(false);

  return (
    <>
      <Button onPress={() => setOpen(true)}>
        Open dialog
      </Button>
      <Modal
        isDismissable
        isOpen={isOpen}
        onOpenChange={setOpen}
      >
        <Dialog>
          <Heading slot="title">Notice</Heading>
          <p>Click outside to close this dialog.</p>
        </Dialog>
      </Modal>
    </>
  );
}
function Example() {
  let [isOpen, setOpen] =
    React.useState(
      false
    );

  return (
    <>
      <Button
        onPress={() =>
          setOpen(true)}
      >
        Open dialog
      </Button>
      <Modal
        isDismissable
        isOpen={isOpen}
        onOpenChange={setOpen}
      >
        <Dialog>
          <Heading slot="title">
            Notice
          </Heading>
          <p>
            Click outside
            to close this
            dialog.
          </p>
        </Dialog>
      </Modal>
    </>
  );
}

Props#


NameTypeDefaultDescription
isEnteringbooleanWhether the modal is currently performing an entry animation.
isExitingbooleanWhether the modal is currently performing an exit animation.
UNSTABLE_portalContainerElementdocument.bodyThe container element in which the overlay portal will be placed. This may have unknown behavior depending on where it is portalled to.
isDismissablebooleanfalseWhether to close the modal when the user interacts outside it.
isKeyboardDismissDisabledbooleanfalseWhether pressing the escape key to close the modal should be disabled.
shouldCloseOnInteractOutside( (element: Element )) => boolean

When user interacts with the argument element outside of the overlay ref, return true if onClose should be called. This gives you a chance to filter out interaction with elements that should not dismiss the overlay. By default, onClose will always be called on interaction outside the overlay ref.

isOpenbooleanWhether the overlay is open by default (controlled).
defaultOpenbooleanWhether the overlay is open by default (uncontrolled).
childrenReactNode( (values: ModalRenderProps{
defaultChildren: ReactNodeundefined
} )) => ReactNode
The children of the component. A function may be provided to alter the children based on component state.
classNamestring( (values: ModalRenderProps{
defaultClassName: stringundefined
} )) => string
The CSS className for the element. A function may be provided to compute the class based on component state.
styleCSSProperties( (values: ModalRenderProps{
defaultStyle: CSSProperties
} )) => CSSPropertiesundefined
The inline style for the element. A function may be provided to compute the style based on component state.
Events
NameTypeDescription
onOpenChange( (isOpen: boolean )) => voidHandler that is called when the overlay's open state changes.
Layout
NameTypeDescription
slotstringnull

A slot name for the component. Slots allow the component to receive props from a parent component. An explicit null value indicates that the local props completely override all props received from a parent.

Styling#


React Aria components can be styled in many ways, including using CSS classes, inline styles, utility classes (e.g. Tailwind), CSS-in-JS (e.g. Styled Components), etc. By default, all components include a builtin className attribute which can be targeted using CSS selectors. These follow the react-aria-ComponentName naming convention.

.react-aria-Modal {
  /* ... */
}
.react-aria-Modal {
  /* ... */
}
.react-aria-Modal {
  /* ... */
}

A custom className can also be specified on any component. This overrides the default className provided by React Aria with your own.

<Modal className="my-modal">
  {/* ... */}
</Modal>
<Modal className="my-modal">
  {/* ... */}
</Modal>
<Modal className="my-modal">
  {/* ... */}
</Modal>

In addition, modals support entry and exit animations, which are exposed as states using DOM attributes that you can target with CSS selectors. Modal and ModalOverlay will automatically wait for any exit animations to complete before removing the element from the DOM.

.react-aria-Modal[data-entering] {
  animation: slide 300ms;
}

.react-aria-Modal[data-exiting] {
  animation: slide 300ms reverse;
}

@keyframes slide {
  /* ... */
}
.react-aria-Modal[data-entering] {
  animation: slide 300ms;
}

.react-aria-Modal[data-exiting] {
  animation: slide 300ms reverse;
}

@keyframes slide {
  /* ... */
}
.react-aria-Modal[data-entering] {
  animation: slide 300ms;
}

.react-aria-Modal[data-exiting] {
  animation: slide 300ms reverse;
}

@keyframes slide {
  /* ... */
}

The className and style props also accept functions which receive states for styling. This lets you dynamically determine the classes or styles to apply, which is useful when using utility CSS libraries like Tailwind.

<Modal className={({isEntering}) => isEntering ? 'slide-in' : ''}>
  {/* ... */}
</Modal>
<Modal
  className={({ isEntering }) =>
    isEntering ? 'slide-in' : ''}
>
  {/* ... */}
</Modal>
<Modal
  className={(
    { isEntering }
  ) =>
    isEntering
      ? 'slide-in'
      : ''}
>
  {/* ... */}
</Modal>

The states, selectors, and render props for each component used in a Modal are documented below.

A Modal can be targeted with the .react-aria-Modal CSS selector, or by overriding with a custom className. It supports the following states and render props:

NameCSS SelectorDescription
isEntering[data-entering]Whether the modal is currently entering. Use this to apply animations.
isExiting[data-exiting]Whether the modal is currently exiting. Use this to apply animations.
stateState of the modal.

ModalOverlay#

By default, Modal includes a builtin ModalOverlay, which renders a backdrop over the page when a modal is open. This can be targeted using the .react-aria-ModalOverlay CSS selector. To customize the ModalOverlay with a different class name or other attributes, render a ModalOverlay and place a Modal inside.

The --visual-viewport-height CSS custom property will be set on the ModalOverlay, which you can use to set the height to account for the virtual keyboard on mobile.

.react-aria-ModalOverlay {
  position: fixed;
  height: var(--visual-viewport-height);
}
.react-aria-ModalOverlay {
  position: fixed;
  height: var(--visual-viewport-height);
}
.react-aria-ModalOverlay {
  position: fixed;
  height: var(--visual-viewport-height);
}

Advanced customization#


Contexts#

All React Aria Components export a corresponding context that can be used to send props to them from a parent element. This enables you to build your own compositional APIs similar to those found in React Aria Components itself. You can send any prop or ref via context that you could pass to the corresponding component. The local props and ref on the component are merged with the ones passed via context, with the local props taking precedence (following the rules documented in mergeProps).

ComponentContextPropsRef
ModalModalContextModalOverlayPropsHTMLDivElement

This example shows a KeyboardModalTrigger component that shows a modal when a user presses a specific key from anywhere on the page. It uses ModalContext to set the open state of the nested modal.

import {ModalContext} from 'react-aria-components';

interface KeyboardModalTriggerProps {
  keyboardShortcut: string,
  children: React.ReactNode
}

function KeyboardModalTrigger(props: KeyboardModalTriggerProps) {
  let [isOpen, setOpen] = React.useState(false);
  React.useEffect(() => {
    let onKeyDown = (e: KeyboardEvent) => {
      if (e.key === props.keyboardShortcut) {
        setOpen(true);
      }
    };

    document.addEventListener('keydown', onKeyDown);
    return () => document.removeEventListener('keydown', onKeyDown);
  }, [props.keyboardShortcut]);

  return (
    <ModalContext.Provider value={{isOpen, onOpenChange: setOpen}}>      {props.children}
    </ModalContext.Provider>
  );
}
import {ModalContext} from 'react-aria-components';

interface KeyboardModalTriggerProps {
  keyboardShortcut: string;
  children: React.ReactNode;
}

function KeyboardModalTrigger(
  props: KeyboardModalTriggerProps
) {
  let [isOpen, setOpen] = React.useState(false);
  React.useEffect(() => {
    let onKeyDown = (e: KeyboardEvent) => {
      if (e.key === props.keyboardShortcut) {
        setOpen(true);
      }
    };

    document.addEventListener('keydown', onKeyDown);
    return () =>
      document.removeEventListener('keydown', onKeyDown);
  }, [props.keyboardShortcut]);

  return (
    <ModalContext.Provider
      value={{ isOpen, onOpenChange: setOpen }}
    >      {props.children}
    </ModalContext.Provider>
  );
}
import {ModalContext} from 'react-aria-components';

interface KeyboardModalTriggerProps {
  keyboardShortcut:
    string;
  children:
    React.ReactNode;
}

function KeyboardModalTrigger(
  props:
    KeyboardModalTriggerProps
) {
  let [isOpen, setOpen] =
    React.useState(
      false
    );
  React.useEffect(() => {
    let onKeyDown = (
      e: KeyboardEvent
    ) => {
      if (
        e.key ===
          props
            .keyboardShortcut
      ) {
        setOpen(true);
      }
    };

    document
      .addEventListener(
        'keydown',
        onKeyDown
      );
    return () =>
      document
        .removeEventListener(
          'keydown',
          onKeyDown
        );
  }, [
    props
      .keyboardShortcut
  ]);

  return (
    <ModalContext.Provider
      value={{
        isOpen,
        onOpenChange:
          setOpen
      }}
    >      {props.children}
    </ModalContext.Provider>
  );
}

The following example uses KeyboardModalTrigger to show a modal when the / key is pressed.

<KeyboardModalTrigger keyboardShortcut="/">
  <Modal isDismissable>
    <Dialog>
      <Heading slot="title">Command palette</Heading>
      <p>Your cool command palette UI here!</p>
    </Dialog>
  </Modal>
</KeyboardModalTrigger>
<KeyboardModalTrigger keyboardShortcut="/">
  <Modal isDismissable>
    <Dialog>
      <Heading slot="title">Command palette</Heading>
      <p>Your cool command palette UI here!</p>
    </Dialog>
  </Modal>
</KeyboardModalTrigger>
<KeyboardModalTrigger keyboardShortcut="/">
  <Modal isDismissable>
    <Dialog>
      <Heading slot="title">
        Command palette
      </Heading>
      <p>
        Your cool
        command palette
        UI here!
      </p>
    </Dialog>
  </Modal>
</KeyboardModalTrigger>

Hooks#

If you need to customize things further, such as accessing internal state or customizing DOM structure, you can drop down to the lower level Hook-based API. See useModalOverlay for more details.