{/* Copyright 2020 Adobe. All rights reserved.
This file is licensed to you under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. You may obtain a copy
of the License at http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under
the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
OF ANY KIND, either express or implied. See the License for the specific language
governing permissions and limitations under the License. */}

import {Layout} from '@react-spectrum/docs';
export default Layout;

import docs from 'docs:react-aria-components';
import {PropTable, HeaderInfo, TypeLink, PageDescription, StateTable, ContextTable} from '@react-spectrum/docs';
import styles from '@react-spectrum/docs/src/docs.css';
import packageData from 'react-aria-components/package.json';
import Anatomy from '/packages/react-aria/docs/overlays/modal-anatomy.svg';
import ChevronRight from '@spectrum-icons/workflow/ChevronRight';
import {Divider} from '@react-spectrum/divider';
import {ExampleCard} from '@react-spectrum/docs/src/ExampleCard';
import {ExampleList} from '@react-spectrum/docs/src/ExampleList';
import {Keyboard} from '@react-spectrum/text';
import {StarterKits} from '@react-spectrum/docs/src/StarterKits';

---
category: Overlays
keywords: [dialog, popover, aria]
type: component
---

# Modal

<PageDescription>{docs.exports.Modal.description}</PageDescription>

<HeaderInfo
  packageData={packageData}
  componentNames={['Modal']}
  sourceData={[
    {type: 'W3C', url: 'https://www.w3.org/WAI/ARIA/apg/patterns/dialogmodal/'}
  ]} />

## Example

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

<DialogTrigger>
  <Button>Sign up…</Button>
  <Modal>
    <Dialog>
      <form>
        <Heading slot="title">Sign up</Heading>
        <TextField autoFocus>
          <Label>First Name: </Label>
          <Input />
        </TextField>
        <TextField>
          <Label>Last Name: </Label>
          <Input />
        </TextField>
        <Button slot="close">
          Submit
        </Button>
      </form>
    </Dialog>
  </Modal>
</DialogTrigger>
```

<details>
  <summary style={{fontWeight: 'bold'}}><ChevronRight size="S" /> Show CSS</summary>
```css hidden
@import './Button.mdx' layer(button);
@import './TextField.mdx' layer(textfield);
@import './Dialog.mdx' layer(dialog);
```

```css
@import "@react-aria/example-theme";

.react-aria-ModalOverlay {
  position: absolute;
  top: 0;
  left: 0;
  width: 100vw;
  height: var(--page-height);
  background: rgba(0 0 0 / .5);
  z-index: 100;

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

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

.react-aria-Modal {
  position: fixed;
  max-height: var(--visual-viewport-height);
  top: calc(var(--visual-viewport-height) / 2);
  left: 50%;
  translate: -50% -50%;
  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;
  width: max-content;
  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);
  }
}
```

</details>

## Features

The HTML [&lt;dialog&gt;](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/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 <Keyboard>Escape</Keyboard> 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](Dialog.html) to create fully accessible modal dialogs. Other overlays such as menus may also be placed in a modal overlay.

## Anatomy

<Anatomy />

A modal consists of an overlay container element, and an underlay. The overlay may contain a [Dialog](Dialog.html), or another element such as a [Menu](Menu.html) or [ListBox](ListBox.html) when used within a component such as a [Select](Select.html) or [ComboBox](ComboBox.html). 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.

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

<ModalOverlay>
  <Modal />
</ModalOverlay>
```

## Examples

<ExampleList tag="modal" />

## 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](https://storybook.js.org/) that you can experiment with, or use as a starting point for your own component library.

<StarterKits component="modal" tailwindComponent="alertdialog" />

## 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.

```tsx example
<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 <Keyboard>Escape</Keyboard> key. This can be disabled with the `isKeyboardDismissDisabled` prop.

```tsx example
<DialogTrigger>
  <Button>Open dialog</Button>
  <Modal isKeyboardDismissDisabled>
    <Dialog>
      <Heading slot="title">Notice</Heading>
      <p>You must close this dialog using the button below.</p>
      <Button slot="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.

```tsx example
import {ModalOverlay} from 'react-aria-components';

<DialogTrigger>
  <Button>Open modal</Button>
  <ModalOverlay className="my-overlay">
    <Modal className="my-modal">
      <Dialog>
        <Heading slot="title">Notice</Heading>
        <p>This is a modal with a custom modal overlay.</p>
        <Button slot="close">Close</Button>
      </Dialog>
    </Modal>
  </ModalOverlay>
</DialogTrigger>
```

<details>
  <summary style={{fontWeight: 'bold'}}><ChevronRight size="S" /> Show CSS</summary>

```css
.my-overlay {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: var(--page-height);
  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: sticky;
  left: 0;
  width: 300px;
  /* Extra padding to account for iOS floating browser UI. */
  top: -100px;
  height: calc(100dvh + 200px);
  padding: 100px 0;
  margin-left: auto;
  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);
  }
}
```

</details>

## 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.

```tsx example
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>
    </>
  );
}
```

## Custom trigger

`DialogTrigger` works out of the box with any pressable React Aria component (e.g. [Button](Button.html), [Link](Link.html), etc.). Custom trigger elements such as third party components and other DOM elements are also supported by wrapping them with the `<Pressable>` component, or using the [usePress](usePress.html) hook.

```tsx example
import {Pressable} from 'react-aria-components';

<DialogTrigger>
  {/*- begin highlight -*/}
  <Pressable>
    <span role="button">Custom trigger</span>
  </Pressable>
  {/*- end highlight -*/}
  <Modal>
    <Dialog>
      <Heading slot="title">Dialog</Heading>
      <p>This dialog was triggered by a custom button.</p>
      <Button slot="close">Close</Button>
    </Dialog>
  </Modal>
</DialogTrigger>
```

Note that any `<Pressable>` child must have an [interactive ARIA role](https://www.w3.org/TR/wai-aria-1.2/#widget_roles) or use an appropriate semantic HTML element so that screen readers can announce the trigger. Trigger components must forward their `ref` and spread all props to a DOM element.

```tsx
const CustomTrigger = React.forwardRef((props, ref) => (
  <button {...props} ref={ref} />
));
```

## Props

<PropTable component={docs.exports.Modal} links={docs.links} />

## 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.

```css
.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.

```jsx
<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. See the [animation guide](styling.html#animation) for more details.

```css render=false
.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](https://tailwindcss.com/).

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

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

### Modal

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:

<StateTable properties={docs.exports.ModalRenderProps.properties} />

### 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 `--page-height` and `--visual-viewport-height` CSS custom property will be set on the `ModalOverlay`, the latter of which you can use to set the height of the Modal to account for the virtual keyboard on mobile.

```css render=false
.react-aria-ModalOverlay {
  position: absolute;
  height: var(--page-height);
}

.react-aria-Modal {
  /* Center modal without adding a extra wrapping div. */
  position: fixed;
  max-height: var(--visual-viewport-height);
  top: calc(var(--visual-viewport-height) / 2);
  left: 50%;
  translate: -50% -50%;
}
```

## 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](mergeProps.html)).

<ContextTable components={['Modal']} docs={docs} />

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.

```tsx example render=false export=true
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 (
    /*- begin highlight -*/
    <ModalContext.Provider value={{isOpen, onOpenChange: setOpen}}>
    {/*- end highlight -*/}
      {props.children}
    </ModalContext.Provider>
  );
}
```

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

```tsx example hidden
<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](useModalOverlay.html) for more details.
