or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

form-components.mdindex.mdnavigation-components.mdoverlay-components.mdselection-components.mdutility-components.md
tile.json

overlay-components.mddocs/

Overlay Components

Modal dialogs, popovers, and tooltips that appear on top of the main content with focus management, portal rendering, and backdrop support.

Capabilities

Dialog

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;
}

DialogPanel

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;
}

DialogBackdrop

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;
}

DialogTitle

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>
  );
}

Popover

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;
}

PopoverButton

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;
}

PopoverPanel

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;
}

PopoverGroup

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 */
}

PopoverBackdrop

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;
}

PopoverOverlay

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>
  );
}

Tooltip (Commented Out)

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;

Common Types

// 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;
}

Focus Management

All overlay components implement proper focus management:

  • Focus Trapping: Focus is trapped within the overlay when open
  • Initial Focus: Can specify which element receives focus when opened
  • Focus Restoration: Focus returns to trigger element when closed
  • Escape Key: ESC key closes overlays by default
  • Click Outside: Clicking outside closes overlays (configurable)

Portal Rendering

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>

Transition Support

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>