Modal dialogs, popovers, and tooltips that appear on top of the main content with focus management, portal rendering, and backdrop support.
A modal dialog component with focus trapping, backdrop support, and automatic accessibility features.
/**
* Modal dialog component with focus management
* @param props - Dialog properties including open state and close handler
*/
function Dialog<TTag extends keyof JSX.IntrinsicElements = 'div'>(
props: DialogProps<TTag>
): JSX.Element;
interface DialogProps<TTag extends keyof JSX.IntrinsicElements = 'div'>
extends PolymorphicProps<TTag> {
/** Whether dialog is open */
open?: boolean;
/** Close handler - called when dialog should close */
onClose: (value: boolean) => void;
/** Initial focus target element */
initialFocus?: React.MutableRefObject<HTMLElement | null>;
/** ARIA role for the dialog */
role?: 'dialog' | 'alertdialog';
/** Whether to auto-focus on open */
autoFocus?: boolean;
/** Whether to use transition animations */
transition?: boolean;
/** Demo mode flag for development */
__demoMode?: boolean;
/** Render prop providing dialog state */
children?: React.ReactNode | ((props: DialogRenderProps) => React.ReactNode);
}
interface DialogRenderProps {
/** Whether dialog is open */
open: boolean;
}The main content container of the dialog.
/**
* Main content container of the dialog
* @param props - DialogPanel properties
*/
function DialogPanel<TTag extends keyof JSX.IntrinsicElements = 'div'>(
props: DialogPanelProps<TTag>
): JSX.Element;
interface DialogPanelProps<TTag extends keyof JSX.IntrinsicElements = 'div'>
extends PolymorphicProps<TTag> {
/** Whether to use transition animations */
transition?: boolean;
/** Render prop providing dialog state */
children?: React.ReactNode | ((props: DialogPanelRenderProps) => React.ReactNode);
}
interface DialogPanelRenderProps {
/** Whether dialog is open */
open: boolean;
}The backdrop/overlay behind the dialog content.
/**
* Backdrop/overlay behind the dialog
* @param props - DialogBackdrop properties
*/
function DialogBackdrop<TTag extends keyof JSX.IntrinsicElements = 'div'>(
props: DialogBackdropProps<TTag>
): JSX.Element;
interface DialogBackdropProps<TTag extends keyof JSX.IntrinsicElements = 'div'>
extends PolymorphicProps<TTag> {
/** Whether to use transition animations */
transition?: boolean;
/** Render prop providing dialog state */
children?: React.ReactNode | ((props: DialogBackdropRenderProps) => React.ReactNode);
}
interface DialogBackdropRenderProps {
/** Whether dialog is open */
open: boolean;
}The title element of the dialog with automatic ARIA labeling.
/**
* Title element of the dialog
* @param props - DialogTitle properties
*/
function DialogTitle<TTag extends keyof JSX.IntrinsicElements = 'h2'>(
props: DialogTitleProps<TTag>
): JSX.Element;
interface DialogTitleProps<TTag extends keyof JSX.IntrinsicElements = 'h2'>
extends PolymorphicProps<TTag> {
/** Render prop providing dialog state */
children?: React.ReactNode | ((props: DialogTitleRenderProps) => React.ReactNode);
}
interface DialogTitleRenderProps {
/** Whether dialog is open */
open: boolean;
}Usage Examples:
import { useState } from "react";
import {
Dialog,
DialogPanel,
DialogTitle,
DialogBackdrop,
Button,
CloseButton
} from "@headlessui/react";
function DialogExample() {
const [isOpen, setIsOpen] = useState(false);
return (
<>
<Button onClick={() => setIsOpen(true)}>
Open Dialog
</Button>
<Dialog open={isOpen} onClose={() => setIsOpen(false)}>
{/* Backdrop */}
<DialogBackdrop className="fixed inset-0 bg-black/30" />
{/* Container to center the panel */}
<div className="fixed inset-0 flex w-screen items-center justify-center p-4">
<DialogPanel className="max-w-lg space-y-4 bg-white p-6 rounded-lg shadow-xl">
<DialogTitle className="font-bold text-xl">
Confirm Delete
</DialogTitle>
<p>Are you sure you want to delete this item? This action cannot be undone.</p>
<div className="flex gap-4">
<Button
onClick={() => setIsOpen(false)}
className="px-4 py-2 bg-gray-200 text-gray-800 rounded hover:bg-gray-300"
>
Cancel
</Button>
<Button
onClick={() => {
// Handle delete
setIsOpen(false);
}}
className="px-4 py-2 bg-red-600 text-white rounded hover:bg-red-700"
>
Delete
</Button>
</div>
{/* Close button in corner */}
<CloseButton className="absolute top-4 right-4 text-gray-400 hover:text-gray-600">
<XMarkIcon className="w-5 h-5" />
</CloseButton>
</DialogPanel>
</div>
</Dialog>
</>
);
}
// Dialog with transition animations
function AnimatedDialogExample() {
const [isOpen, setIsOpen] = useState(false);
return (
<Dialog open={isOpen} onClose={() => setIsOpen(false)} transition>
<DialogBackdrop
transition
className="fixed inset-0 bg-black/30 duration-300 ease-out data-[closed]:opacity-0"
/>
<div className="fixed inset-0 flex w-screen items-center justify-center p-4">
<DialogPanel
transition
className="max-w-lg bg-white p-6 rounded-lg shadow-xl duration-300 ease-out
data-[closed]:transform data-[closed]:scale-95 data-[closed]:opacity-0"
>
<DialogTitle>Animated Dialog</DialogTitle>
<p>This dialog has smooth animations.</p>
</DialogPanel>
</div>
</Dialog>
);
}A popover component for displaying floating content triggered by user interaction.
/**
* Popover component for floating content
* @param props - Popover properties
*/
function Popover<TTag extends keyof JSX.IntrinsicElements = 'div'>(
props: PopoverProps<TTag>
): JSX.Element;
interface PopoverProps<TTag extends keyof JSX.IntrinsicElements = 'div'>
extends PolymorphicProps<TTag> {
/** Demo mode flag for development */
__demoMode?: boolean;
/** Render prop providing popover state */
children?: React.ReactNode | ((props: PopoverRenderProps) => React.ReactNode);
}
interface PopoverRenderProps {
/** Whether popover is open */
open: boolean;
/** Function to close popover */
close: (focusableElement?: HTMLElement | React.MutableRefObject<HTMLElement | null>) => void;
}Button to toggle the popover display.
/**
* Button to toggle the popover
* @param props - PopoverButton properties
*/
function PopoverButton<TTag extends keyof JSX.IntrinsicElements = 'button'>(
props: PopoverButtonProps<TTag>
): JSX.Element;
interface PopoverButtonProps<TTag extends keyof JSX.IntrinsicElements = 'button'>
extends PolymorphicProps<TTag> {
/** Whether button is disabled */
disabled?: boolean;
/** Whether to auto-focus on mount */
autoFocus?: boolean;
/** Render prop providing button state */
children?: React.ReactNode | ((props: PopoverButtonRenderProps) => React.ReactNode);
}
interface PopoverButtonRenderProps {
/** Whether popover is open */
open: boolean;
/** Whether button is active/pressed */
active: boolean;
/** Whether being hovered */
hover: boolean;
/** Whether has focus */
focus: boolean;
/** Whether disabled */
disabled: boolean;
/** Whether has autofocus */
autofocus: boolean;
}The floating panel content of the popover.
/**
* Floating panel content of the popover
* @param props - PopoverPanel properties
*/
function PopoverPanel<TTag extends keyof JSX.IntrinsicElements = 'div'>(
props: PopoverPanelProps<TTag>
): JSX.Element;
interface PopoverPanelProps<TTag extends keyof JSX.IntrinsicElements = 'div'>
extends PolymorphicProps<TTag> {
/** Whether panel should receive focus */
focus?: boolean;
/** Floating UI anchor configuration */
anchor?: AnchorProps;
/** Whether to render in portal */
portal?: boolean;
/** Whether to use modal behavior */
modal?: boolean;
/** Whether to use transition animations */
transition?: boolean;
/** Render prop providing panel state */
children?: React.ReactNode | ((props: PopoverPanelRenderProps) => React.ReactNode);
}
interface PopoverPanelRenderProps {
/** Whether popover is open */
open: boolean;
/** Function to close popover */
close: (focusableElement?: HTMLElement | React.MutableRefObject<HTMLElement | null>) => void;
}Groups multiple popovers together for coordinated behavior.
/**
* Groups multiple popovers together
* @param props - PopoverGroup properties
*/
function PopoverGroup<TTag extends keyof JSX.IntrinsicElements = 'div'>(
props: PopoverGroupProps<TTag>
): JSX.Element;
interface PopoverGroupProps<TTag extends keyof JSX.IntrinsicElements = 'div'>
extends PolymorphicProps<TTag> {
/** Standard group properties */
}Backdrop component for popover overlays.
/**
* Backdrop for popover overlay
* @param props - PopoverBackdrop properties
*/
function PopoverBackdrop<TTag extends keyof JSX.IntrinsicElements = 'div'>(
props: PopoverBackdropProps<TTag>
): JSX.Element;
interface PopoverBackdropProps<TTag extends keyof JSX.IntrinsicElements = 'div'>
extends PolymorphicProps<TTag> {
/** Whether to use transition animations */
transition?: boolean;
/** Render prop providing backdrop state */
children?: React.ReactNode | ((props: PopoverBackdropRenderProps) => React.ReactNode);
}
interface PopoverBackdropRenderProps {
/** Whether popover is open */
open: boolean;
}Overlay component for popover modal behavior.
/**
* Overlay component for popover
* @param props - PopoverOverlay properties
*/
function PopoverOverlay<TTag extends keyof JSX.IntrinsicElements = 'div'>(
props: PopoverOverlayProps<TTag>
): JSX.Element;
interface PopoverOverlayProps<TTag extends keyof JSX.IntrinsicElements = 'div'>
extends PolymorphicProps<TTag> {
/** Whether to use transition animations */
transition?: boolean;
/** Render prop providing overlay state */
children?: React.ReactNode | ((props: PopoverOverlayRenderProps) => React.ReactNode);
}
interface PopoverOverlayRenderProps {
/** Whether popover is open */
open: boolean;
}Usage Examples:
import {
Popover,
PopoverButton,
PopoverPanel,
PopoverGroup
} from "@headlessui/react";
function PopoverExample() {
return (
<Popover>
<PopoverButton className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600">
Options
</PopoverButton>
<PopoverPanel
anchor="bottom start"
className="flex flex-col bg-white border border-gray-200 rounded-lg shadow-lg p-2 mt-2"
>
{({ close }) => (
<>
<button
onClick={() => {
console.log('Edit clicked');
close();
}}
className="px-3 py-2 text-left hover:bg-gray-100 rounded"
>
Edit
</button>
<button
onClick={() => {
console.log('Delete clicked');
close();
}}
className="px-3 py-2 text-left hover:bg-gray-100 rounded text-red-600"
>
Delete
</button>
</>
)}
</PopoverPanel>
</Popover>
);
}
// Navigation popover with grouped behavior
function NavigationPopover() {
const solutions = [
{ name: 'Analytics', description: 'Get insights into your traffic' },
{ name: 'Engagement', description: 'Speak directly to your customers' },
{ name: 'Security', description: 'Keep your data safe and secure' },
];
return (
<PopoverGroup className="flex space-x-10">
<Popover>
<PopoverButton className="text-gray-900 hover:text-gray-700">
Solutions
</PopoverButton>
<PopoverPanel
anchor="bottom start"
className="absolute z-10 mt-3 w-screen max-w-md transform px-2 sm:px-0"
>
<div className="overflow-hidden rounded-lg shadow-lg ring-1 ring-black ring-opacity-5">
<div className="relative grid gap-6 bg-white px-5 py-6 sm:gap-8 sm:p-8">
{solutions.map((item) => (
<a
key={item.name}
href="#"
className="-m-3 flex items-start rounded-lg p-3 hover:bg-gray-50"
>
<div className="ml-4">
<p className="text-base font-medium text-gray-900">
{item.name}
</p>
<p className="mt-1 text-sm text-gray-500">
{item.description}
</p>
</div>
</a>
))}
</div>
</div>
</PopoverPanel>
</Popover>
{/* Additional popovers in the group */}
<Popover>
<PopoverButton className="text-gray-900 hover:text-gray-700">
Pricing
</PopoverButton>
<PopoverPanel anchor="bottom start">
{/* Pricing content */}
</PopoverPanel>
</Popover>
</PopoverGroup>
);
}
// Modal popover with backdrop
function ModalPopoverExample() {
return (
<Popover>
<PopoverButton>Open Modal Popover</PopoverButton>
<PopoverBackdrop className="fixed inset-0 bg-black/20" />
<PopoverPanel
modal
className="fixed top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2
bg-white p-6 rounded-lg shadow-xl max-w-sm w-full mx-4"
>
<h3 className="text-lg font-medium mb-4">Modal Popover</h3>
<p className="text-gray-600 mb-4">This popover has modal behavior with a backdrop.</p>
{({ close }) => (
<button
onClick={() => close()}
className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
>
Close
</button>
)}
</PopoverPanel>
</Popover>
);
}Note: Tooltip component is commented out in the current version (v2.2.7) but may be available in future versions.
// TODO: Enable when ready (from source comments)
// function Tooltip(props: TooltipProps): JSX.Element;// Floating UI anchor configuration for positioning
interface AnchorProps {
/** Anchor position relative to trigger */
to?:
| 'top' | 'top start' | 'top end'
| 'right' | 'right start' | 'right end'
| 'bottom' | 'bottom start' | 'bottom end'
| 'left' | 'left start' | 'left end';
/** Gap between trigger and floating element */
gap?: number;
/** Offset from default position */
offset?: number;
/** Padding from viewport edges */
padding?: number;
}
// Common close function type for overlay components
type CloseFunction = (
focusableElement?: HTMLElement | React.MutableRefObject<HTMLElement | null>
) => void;
// Base overlay render props
interface BaseOverlayRenderProps {
open: boolean;
close: CloseFunction;
}All overlay components implement proper focus management:
Overlay components can render in portals to avoid z-index and overflow issues:
// Render in portal (outside normal DOM tree)
<DialogPanel portal className="...">
Content renders at document body level
</DialogPanel>
// Render in-place (default)
<DialogPanel className="...">
Content renders where declared
</DialogPanel>All overlay components support smooth transitions:
<Dialog open={isOpen} onClose={setIsOpen} transition>
<DialogBackdrop
transition
className="duration-300 ease-out data-[closed]:opacity-0"
/>
<DialogPanel
transition
className="duration-300 ease-out data-[closed]:scale-95 data-[closed]:opacity-0"
>
{/* Content */}
</DialogPanel>
</Dialog>