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 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
DialogAnchorfor anchored popups andGlobalModalfor modal content. - Keep dialog IDs stable so focus and open state are preserved across renders.
- Read open state through
useDialogIsOpenfor accurate ARIA attributes. - Use the nearest dialog manager when integrating with SDK surfaces such as
AttachmentSelector, reaction selectors, and message actions. - 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
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 MessageInput, 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 id="custom-dialog-manager">
{children}
</DialogManagerProvider>
);Callouts, Context Menus, Prompts, And Alerts
The SDK ships higher-level dialog primitives built on the same manager system:
CalloutContextMenuPromptAlert
Use them when your custom UI matches one of those patterns instead of rebuilding focus, placement, and dismissal logic yourself.
Modal Surfaces
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,
MessageInput,
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>
<MessageInput />
</Channel>
</WithComponents>
);