# Modal

The SDK exports two modal components:

1. `Modal` (legacy)
2. `GlobalModal` (introduced with stream-chat-react@13.6.0)

## Best Practices

- Prefer `GlobalModal` to avoid clipping in complex layouts.
- Keep modal content lightweight to prevent scroll jank.
- Use `onCloseAttempt` to enforce confirmation flows when needed.
- Ensure `open` state is controlled by a single source of truth.
- Verify keyboard and overlay close behavior for accessibility.

<admonition type="note">

Modals use `position: fixed`. The legacy `Modal` renders deep in the tree (for example under `Message`), which in Safari can cause clipping if a parent uses `position: relative`. To avoid this, use `GlobalModal`, which renders at the top of the SDK tree.

</admonition>

By default, the SDK uses `Modal`. Opt in to `GlobalModal` by passing it to the `Channel` `Modal` prop:

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

const Component = () => <Channel Modal={GlobalModal}></Channel>;
```

## Modal props

Both modals accept the same props.

### children

The `children` prop is used to render the modal content.

```tsx
import { useCallback, useState } from "react";

const Component = ({ image }) => {
  const [modalIsOpen, setModalIsOpen] = useState(false);
  const closeModal = useCallback(() => {
    setModalIsOpen(false);
  }, []);

  return (
    <>
      <button onClick={() => setModalIsOpen(true)}>Open gallery</button>
      <Modal
        className="str-chat__image-modal"
        onClose={closeModal}
        open={modalIsOpen}
      >
        <ModalGallery images={[image]} index={0} />
      </Modal>
    </>
  );
};
```

### open

Flag to determine whether the modal and its content is rendered.

| Type      | Default |
| --------- | ------- |
| `boolean` | `false` |

### className

Class attached to the modal overlay div with default class `str-chat__modal`.

| Type     |
| -------- |
| `string` |

### onClose

`onClose` should update `open` to hide the modal (typically set to `false`).

| Type                               |
| ---------------------------------- |
| `(event: ModalCloseEvent) => void` |

Where `ModalCloseEvent` is defined as follows:

```ts
export type ModalCloseEvent =
  | KeyboardEvent
  | React.KeyboardEvent
  | React.MouseEvent<HTMLButtonElement | HTMLDivElement>;
```

### onCloseAttempt

Optional handler to intercept closing logic. Return `false` to prevent `onClose`. `onCloseAttempt` recognizes:

1. `overlay` - click on overlay
2. `button` - click on close button
3. `escape` - pressing `Escape` key

By default, modals are closed upon interacting with all the above sources.

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

// click on overlay will not result in closing the modal
const onCloseAttempt: (source: ModalCloseSource) => boolean = (source) =>
  source !== "overlay";

const GlobalModalWrapper = (props: ModalProps) => {
  return <GlobalModal {...props} onCloseAttempt={onCloseAttempt} />;
};

const Component = () => (
  <Channel Modal={GlobalModalWrapper}>{/*    children*/}</Channel>
);
```

| Type                                                            |
| --------------------------------------------------------------- |
| `(source: ModalCloseSource, event: ModalCloseEvent) => boolean` |


---

This page was last updated at 2026-03-06T17:06:44.433Z.

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