# Modal

> **Info:** This is release candidate documentation for **Stream Chat React SDK v15**. For the latest stable version, see the [latest version (v14)](https://getstream.io/chat/docs/sdk/react/).

`GlobalModal` renders a modal on the modal dialog manager that `Chat` provides, and the SDK's own modals go through it. [Modals](https://getstream.io/chat/docs/sdk/react/v15/ui/customizing/dialogs/#modals) explains how it fits with the other dialogs.

## Best practices

- Render it inside `Chat`: the modal dialog manager comes from there.
- Control it with one `open` state, and set that state to `false` in `onClose`.
- Use `onCloseAttempt` to keep the modal open, for example while a form has unsaved changes.
- Give it a label: pass `aria-label` or `aria-labelledby`, or use the SDK's `Prompt`, `Alert` or `Viewer`, whose header labels it.
- To change every SDK modal at once, replace the `Modal` override key, and keep the `className` the SDK passes.

## Basic usage

```tsx
import { useCallback, useState } from "react";
import { GlobalModal } from "stream-chat-react";

export const Example = () => {
  const [open, setOpen] = useState(false);
  const closeModal = useCallback(() => setOpen(false), []);

  return (
    <>
      <button onClick={() => setOpen(true)} type="button">
        Open modal
      </button>
      <GlobalModal aria-label="Example" onClose={closeModal} open={open}>
        <div className="custom-modal-body">Custom content</div>
      </GlobalModal>
    </>
  );
};
```

The user closes it with Escape, a click on the overlay, or the close button passed as `CloseButtonOnOverlay`. Each calls `onCloseAttempt`, then `onClose`. When modals stack, only the one opened last responds and holds the focus. Focus returns where it was when the modal closes.

Inside the modal, `useModalContext()` returns `close`, which closes it the same way as the close button, and `dialogId`. Outside a modal, `close` does nothing.

## SDK modal overrides

The SDK renders its own modals through the `Modal` override key: delete confirmations, bounced messages, poll creation and poll actions, the attachment selector's dialogs, channel details and the image gallery. A component registered with `WithComponents` replaces every one of them in that subtree. It receives `ModalProps`, including a `className` of the SDK's own, such as `str-chat__message-bounce-modal`, so keep it next to yours:

```tsx
import type { PropsWithChildren } from "react";
import {
  Channel,
  GlobalModal,
  MessageComposer,
  WithComponents,
} from "stream-chat-react";
import type { ModalProps } from "stream-chat-react";

const AppModal = ({ className, ...props }: PropsWithChildren<ModalProps>) => (
  <GlobalModal {...props} className={`app-modal ${className ?? ""}`} />
);

export const App = () => (
  <WithComponents overrides={{ Modal: AppModal }}>
    <Channel channel={channel}>
      <MessageComposer />
    </Channel>
  </WithComponents>
);
```

## Props

| Prop                     | Type                                                                                           | Default                    | Description                                                                                                                                                                |
| ------------------------ | ---------------------------------------------------------------------------------------------- | -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `open`                   | `boolean`                                                                                      | required                   | Whether the modal is open.                                                                                                                                                 |
| `onClose`                | `(event: ModalCloseEvent) => void`                                                             |                            | Called when the modal closes. Set `open` to `false` here.                                                                                                                  |
| `onCloseAttempt`         | `(source: ModalCloseSource, event: ModalCloseEvent) => boolean`                                |                            | Called before closing. Return `false` to keep the modal open. `source` is `"overlay"`, `"button"` or `"escape"`.                                                           |
| `children`               | `ReactNode`                                                                                    |                            | The modal's content.                                                                                                                                                       |
| `className`              | `string`                                                                                       |                            | Added to the overlay, next to `str-chat`, the `theme` from `Chat` and `str-chat__modal`.                                                                                   |
| `CloseButtonOnOverlay`   | `ComponentType<ComponentProps<"button">>`                                                      |                            | A close button rendered on the overlay, outside the dialog. It receives `onClick` and `ref`.                                                                               |
| `aria-label`             | `string`                                                                                       |                            | The dialog's accessible name. Ignored when `aria-labelledby` is passed.                                                                                                    |
| `aria-labelledby`        | `string`                                                                                       | `"<dialogId>-title"`       | The id of the element that names the dialog. Without it or `aria-label`, the title of a `Prompt`, `Alert` or `Viewer` header inside the modal names it.                    |
| `aria-describedby`       | `string`                                                                                       | `"<dialogId>-description"` | The id of the element that describes the dialog.                                                                                                                           |
| `role`                   | `"dialog" \| "alertdialog"`                                                                    | `"dialog"`                 | The dialog's role.                                                                                                                                                         |
| `initialFocusStrategy`   | `"dialog" \| "firstElement"`                                                                   | `"firstElement"`           | What takes the focus when the modal opens: its first focusable element, or the dialog itself. With `"dialog"`, Enter on the dialog focuses its `[data-autofocus]` element. |
| `getInitialFocusElement` | `(dialogElement: HTMLDivElement) => HTMLElement \| null`                                       |                            | Picks the element to focus when the modal opens, instead of `initialFocusStrategy`. Returning `null` focuses the dialog.                                                   |
| `dialogId`               | `string`                                                                                       | a generated id             | The modal's id on the modal dialog manager. The default ids of `aria-labelledby` and `aria-describedby` are built from it.                                                 |
| `dialogRootProps`        | `ComponentProps<"div">` without `aria-label`, `aria-labelledby`, `aria-describedby` and `role` |                            | Props for the dialog element inside the overlay. Its `className` is added to `str-chat__modal__dialog`, and its `onKeyDown` runs before the modal's own.                   |

`ModalCloseEvent` is `KeyboardEvent`, `React.KeyboardEvent`, or `React.MouseEvent` on a button or `div`. `useModalContext().close()` passes an empty event.

## Examples

### Intercept close attempts

This modal closes only through its button or Escape, not a click on the overlay:

```tsx
import { GlobalModal } from "stream-chat-react";
import type { ModalCloseSource, ModalProps } from "stream-chat-react";
import type { PropsWithChildren } from "react";

const onCloseAttempt = (source: ModalCloseSource) => source !== "overlay";

export const ConfirmingModal = (props: PropsWithChildren<ModalProps>) => (
  <GlobalModal {...props} onCloseAttempt={onCloseAttempt} />
);
```

### Focus a field when the modal opens

```tsx
import { GlobalModal } from "stream-chat-react";

const focusNameField = (dialog: HTMLDivElement) =>
  dialog.querySelector<HTMLElement>("input[name='name']");

export const RenameModal = ({
  onClose,
  open,
}: {
  onClose: () => void;
  open: boolean;
}) => (
  <GlobalModal
    aria-label="Rename"
    getInitialFocusElement={focusNameField}
    onClose={onClose}
    open={open}
  >
    <input aria-label="Name" name="name" />
  </GlobalModal>
);
```

---

For the most recent version of this documentation, visit [https://getstream.io/chat/docs/sdk/react/v15/component-reference/utilities/modal/](https://getstream.io/chat/docs/sdk/react/v15/component-reference/utilities/modal/).