This is beta documentation for Stream Chat React SDK v14. For the latest stable version, see the latest version (v13) .

Dialog Management

Dialog management in the React SDK covers anchored popups such as menus and callouts, plus full-screen modal surfaces such as GlobalModal.

Best Practices

  • Use DialogAnchor for anchored popups and GlobalModal for modal content.
  • Keep dialog IDs stable so focus and open state are preserved across renders.
  • Read open state through useDialogIsOpen for accurate ARIA attributes.
  • Use the nearest dialog manager when integrating with SDK surfaces such as AttachmentSelector, reaction selectors, and message actions.
  • Decide your outside-click dismissal policy at the dialog-manager level first, then override it per popup only when one surface needs different behavior.
  • Keep popup content small and delegate larger flows to prompts or modals.

Anchored Dialogs

Anchored dialogs are positioned relative to a reference element and rendered through a dialog manager.

Rendering a dialog

import { useRef } from "react";
import { DialogAnchor, useDialog, useDialogIsOpen } from "stream-chat-react";

const dialogId = "custom-help-dialog";

const HelpMenu = () => {
  const buttonRef = useRef<HTMLButtonElement | null>(null);
  const dialog = useDialog({ id: dialogId });
  const dialogIsOpen = useDialogIsOpen(dialogId);

  return (
    <>
      <button
        aria-expanded={dialogIsOpen}
        onClick={() => dialog.toggle()}
        ref={buttonRef}
      >
        Toggle help
      </button>
      <DialogAnchor
        id={dialogId}
        placement="top-start"
        referenceElement={buttonRef.current}
        trapFocus
      >
        <div className="custom-help-dialog">Help content</div>
      </DialogAnchor>
    </>
  );
};

Dialog API

useDialog() returns a dialog controller with:

  • open()
  • close()
  • toggle()
  • remove()

Use useDialogIsOpen(id) to subscribe to open-state changes and wire aria-expanded or other UI state.

Dialog Managers

Anchored dialogs render through the nearest DialogManagerProvider. SDK surfaces such as MessageComposer, MessageList, and Chat already create the dialog-manager layers they need.

If you build a custom popup subtree outside those surfaces, add your own manager:

import { DialogManagerProvider } from "stream-chat-react";

const CustomDialogArea = ({ children }) => (
  <DialogManagerProvider closeOnClickOutside={false} id="custom-dialog-manager">
    {children}
  </DialogManagerProvider>
);

DialogManagerProvider controls the default outside-click dismissal policy for every anchored dialog in that subtree. Set closeOnClickOutside={false} when the whole subtree should stay open until code closes it explicitly.

Outside-Click And Transition Control

Use DialogAnchor props when one anchored surface needs different dismissal or exit-animation behavior than the rest of the manager subtree.

import { useRef } from "react";
import { DialogAnchor, useDialog } from "stream-chat-react";

const dialogId = "custom-help-dialog";

const HelpMenu = () => {
  const buttonRef = useRef<HTMLButtonElement | null>(null);
  const dialog = useDialog({ id: dialogId });

  return (
    <>
      <button onClick={() => dialog.toggle()} ref={buttonRef}>
        Toggle help
      </button>
      <DialogAnchor
        closeOnClickOutside={false}
        closeTransitionMs={180}
        id={dialogId}
        placement="top-start"
        referenceElement={buttonRef.current}
      >
        <div className="custom-help-dialog">Help content</div>
      </DialogAnchor>
    </>
  );
};

Use:

  • closeOnClickOutside to override the manager default for one anchored dialog
  • closeTransitionMs to keep the dialog mounted long enough for exit animations to finish

Callouts, Context Menus, Prompts, And Alerts

The SDK ships higher-level dialog primitives built on the same manager system:

  • Callout
  • ContextMenu
  • Prompt
  • Alert

Use them when your custom UI matches one of those patterns instead of rebuilding focus, placement, and dismissal logic yourself.

If you need to customize the SDK menu shell itself, override ContextMenu or ContextMenuContent through WithComponents:

import { ContextMenu, WithComponents } from "stream-chat-react";
import type { ContextMenuProps } from "stream-chat-react";

const CustomContextMenu = (props: ContextMenuProps) => (
  <ContextMenu {...props} closeOnClickOutside={false} closeTransitionMs={180} />
);

const App = ({ children }) => (
  <WithComponents overrides={{ ContextMenu: CustomContextMenu }}>
    {children}
  </WithComponents>
);

Wrap ContextMenuContent instead when you want to customize submenu content, headers, or back-navigation rendering without replacing the anchoring and dismissal behavior from ContextMenu.

GlobalModal is the modal primitive used for full-screen or overlay flows. Chat already mounts the modal dialog manager required by GlobalModal.

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

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

  return (
    <>
      <button onClick={() => setOpen(true)}>Open modal</button>
      <GlobalModal onClose={close} open={open}>
        <div className="custom-modal-body">Modal content</div>
      </GlobalModal>
    </>
  );
};

For SDK-owned modal flows such as attachment-selector dialogs or delete confirmations, override the shared Modal component with WithComponents:

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

const CustomModal = (props: ModalProps) => (
  <GlobalModal {...props} className="custom-modal-shell" />
);

const App = () => (
  <WithComponents overrides={{ Modal: CustomModal }}>
    <Channel>
      <MessageComposer />
    </Channel>
  </WithComponents>
);