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

utility-components.mddocs/

Utility Components

Supporting components and utilities including focus management, portals, labels, descriptions, transitions, and interactive state helpers.

Capabilities

FocusTrap

A component that traps focus within its boundaries, essential for modal dialogs and other overlay components.

/**
 * Component that traps focus within its boundaries
 * @param props - FocusTrap properties including feature configuration
 */
function FocusTrap<TTag extends keyof JSX.IntrinsicElements = 'div'>(
  props: FocusTrapProps<TTag>
): JSX.Element;

interface FocusTrapProps<TTag extends keyof JSX.IntrinsicElements = 'div'> 
  extends PolymorphicProps<TTag> {
  /** Initial focus target element */
  initialFocus?: React.MutableRefObject<HTMLElement | null>;
  /** Fallback focus target if initial focus fails */
  initialFocusFallback?: React.MutableRefObject<HTMLElement | null>;
  /** Feature flags controlling focus trap behavior */
  features?: FocusTrapFeatures;
  /** Additional containers to include in focus trap */
  containers?: Containers;
}

enum FocusTrapFeatures {
  /** No special features */
  None = 0,
  /** Move focus to initial element on mount */
  InitialFocus = 1,
  /** Trap tab navigation within boundaries */
  TabLock = 2,
  /** Lock programmatic focus changes */
  FocusLock = 4,
  /** Restore focus to previous element on unmount */
  RestoreFocus = 8,
  /** Look for data-autofocus attribute */
  AutoFocus = 16
}

type Containers = React.MutableRefObject<Set<React.MutableRefObject<HTMLElement | null>>>;

Usage Examples:

import { FocusTrap, FocusTrapFeatures } from "@headlessui/react";
import { useRef } from "react";

function FocusTrapExample() {
  const initialFocusRef = useRef(null);
  
  return (
    <FocusTrap
      initialFocus={initialFocusRef}
      features={
        FocusTrapFeatures.InitialFocus |
        FocusTrapFeatures.TabLock |
        FocusTrapFeatures.RestoreFocus
      }
      className="bg-white p-6 rounded-lg shadow-lg"
    >
      <h2 className="text-lg font-semibold mb-4">Focus Trapped Content</h2>
      
      <input 
        ref={initialFocusRef}
        placeholder="This gets initial focus"
        className="w-full p-2 border border-gray-300 rounded mb-4"
      />
      
      <button className="px-4 py-2 bg-blue-500 text-white rounded mr-2">
        Button 1
      </button>
      <button className="px-4 py-2 bg-green-500 text-white rounded">
        Button 2
      </button>
      
      {/* Focus will cycle between these elements only */}
    </FocusTrap>
  );
}

// Advanced focus trap with multiple containers
function AdvancedFocusTrapExample() {
  const containersRef = useRef(new Set());
  const sidebarRef = useRef(null);
  const mainRef = useRef(null);
  
  // Add containers to the set
  React.useEffect(() => {
    containersRef.current.add(sidebarRef);
    containersRef.current.add(mainRef);
  }, []);
  
  return (
    <FocusTrap
      containers={containersRef}
      features={FocusTrapFeatures.TabLock | FocusTrapFeatures.RestoreFocus}
    >
      <div className="flex">
        <div ref={sidebarRef} className="w-64 bg-gray-100 p-4">
          <button>Sidebar Button</button>
        </div>
        <div ref={mainRef} className="flex-1 p-4">
          <button>Main Button</button>
        </div>
      </div>
    </FocusTrap>
  );
}

Portal

A portal component for rendering content outside the normal DOM tree to avoid z-index and overflow issues.

/**
 * Portal component for rendering outside normal DOM tree
 * @param props - Portal properties including target document
 */
function Portal(props: PortalProps): JSX.Element;

interface PortalProps {
  /** Whether portal is enabled */
  enabled?: boolean;
  /** Target document for portal rendering */
  ownerDocument?: Document | null;
  /** Content to render in portal */
  children: React.ReactNode;
}

PortalGroup

Groups multiple portals together for coordinated behavior.

/**
 * Groups multiple portals together
 * @param props - PortalGroup properties
 */
function PortalGroup(props: PortalGroupProps): JSX.Element;

interface PortalGroupProps {
  /** Target element for grouped portals */
  target?: HTMLElement;
  /** Content containing portals */
  children: React.ReactNode;
}

useNestedPortals

Hook for managing nested portal relationships.

/**
 * Hook for managing nested portals
 * @returns Portal management utilities
 */
function useNestedPortals(): {
  /** Register a nested portal */
  registerPortal: (element: HTMLElement) => void;
  /** Unregister a nested portal */
  unregisterPortal: (element: HTMLElement) => void;
  /** Check if element is within a nested portal */
  isWithinPortal: (element: HTMLElement) => boolean;
};

Usage Examples:

import { Portal, PortalGroup } from "@headlessui/react";
import { useState } from "react";

function PortalExample() {
  const [showTooltip, setShowTooltip] = useState(false);
  
  return (
    <div className="p-4">
      <button
        onMouseEnter={() => setShowTooltip(true)}
        onMouseLeave={() => setShowTooltip(false)}
        className="px-4 py-2 bg-blue-500 text-white rounded"
      >
        Hover me
      </button>
      
      {/* This renders at document body level */}
      <Portal enabled={showTooltip}>
        <div className="absolute top-20 left-20 bg-black text-white p-2 rounded shadow-lg">
          This tooltip is rendered in a portal!
        </div>
      </Portal>
    </div>
  );
}

// Conditional portal usage
function ConditionalPortalExample() {
  const [usePortal, setUsePortal] = useState(true);
  
  const content = (
    <div className="bg-red-100 p-4 rounded">
      This content may or may not be in a portal
    </div>
  );
  
  return (
    <div>
      <label className="flex items-center mb-4">
        <input
          type="checkbox"
          checked={usePortal}
          onChange={(e) => setUsePortal(e.target.checked)}
          className="mr-2"
        />
        Use Portal
      </label>
      
      {usePortal ? (
        <Portal>{content}</Portal>
      ) : (
        content
      )}
    </div>
  );
}

// Portal group example
function PortalGroupExample() {
  return (
    <PortalGroup>
      <div className="space-y-4">
        <Portal>
          <div className="fixed top-4 right-4 bg-green-500 text-white p-2 rounded">
            Notification 1
          </div>
        </Portal>
        
        <Portal>
          <div className="fixed top-16 right-4 bg-blue-500 text-white p-2 rounded">
            Notification 2
          </div>
        </Portal>
      </div>
    </PortalGroup>
  );
}

Label

A label component that automatically associates with form controls and provides proper accessibility.

/**
 * Label component with automatic form control association
 * @param props - Label properties including passive mode
 */
function Label<TTag extends keyof JSX.IntrinsicElements = 'label'>(
  props: LabelProps<TTag>
): JSX.Element;

interface LabelProps<TTag extends keyof JSX.IntrinsicElements = 'label'> 
  extends PolymorphicProps<TTag> {
  /** Whether to disable click behavior (passive mode) */
  passive?: boolean;
  /** Explicit control ID to associate with */
  htmlFor?: string;
}

useLabelledBy

Hook to get label IDs for aria-labelledby attribute.

/**
 * Hook to get label IDs for aria-labelledby
 * @returns Comma-separated string of label IDs
 */
function useLabelledBy(): string | undefined;

useLabels

Hook for managing label associations.

/**
 * Hook for managing label associations
 * @returns Label management utilities
 */
function useLabels(): {
  /** Register a label */
  register: (id: string) => void;
  /** Unregister a label */
  unregister: (id: string) => void;
  /** Get all label IDs */
  labelIds: string[];
};

Usage Examples:

import { Label, Field, Input, useLabelledBy } from "@headlessui/react";

function LabelExample() {
  return (
    <Field className="space-y-2">
      <Label className="block text-sm font-medium text-gray-700">
        Email Address
      </Label>
      <Input 
        type="email" 
        className="w-full px-3 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500"
      />
    </Field>
  );
}

// Multiple labels for one control
function MultipleLabelExample() {
  return (
    <Field>
      <div className="space-y-1">
        <Label className="block text-sm font-medium text-gray-700">
          Password
        </Label>
        <Label passive className="block text-xs text-gray-500">
          Must be at least 8 characters
        </Label>
      </div>
      <Input 
        type="password"
        className="mt-1 w-full px-3 py-2 border border-gray-300 rounded-md"
      />
    </Field>
  );
}

// Using label hooks
function CustomControlWithLabel() {
  const labelIds = useLabelledBy();
  
  return (
    <div>
      <Label>Custom Control</Label>
      <Label passive>Additional context</Label>
      
      <div
        role="slider"
        aria-labelledby={labelIds}
        tabIndex={0}
        className="w-32 h-6 bg-gray-200 rounded-full cursor-pointer"
      >
        {/* Custom slider implementation */}
      </div>
    </div>
  );
}

Description

A description component that automatically associates with form controls for accessibility.

/**
 * Description component with automatic form control association
 * @param props - Description properties
 */
function Description<TTag extends keyof JSX.IntrinsicElements = 'p'>(
  props: DescriptionProps<TTag>
): JSX.Element;

interface DescriptionProps<TTag extends keyof JSX.IntrinsicElements = 'p'> 
  extends PolymorphicProps<TTag> {
  /** Standard description properties */
}

useDescribedBy

Hook to get description ID for aria-describedby attribute.

/**
 * Hook to get description ID for aria-describedby
 * @returns Description ID string
 */
function useDescribedBy(): string | undefined;

Usage Examples:

import { Description, Field, Label, Input, useDescribedBy } from "@headlessui/react";

function DescriptionExample() {
  return (
    <Field className="space-y-2">
      <Label className="block text-sm font-medium text-gray-700">
        Username
      </Label>
      <Input 
        className="w-full px-3 py-2 border border-gray-300 rounded-md"
        placeholder="Enter username"
      />
      <Description className="text-sm text-gray-500">
        Username must be unique and contain only letters, numbers, and underscores.
      </Description>
    </Field>
  );
}

// Multiple descriptions
function MultipleDescriptionExample() {
  return (
    <Field>
      <Label>Password</Label>
      <Input type="password" />
      <Description className="text-sm text-gray-600">
        Password requirements:
      </Description>
      <Description className="text-xs text-gray-500 ml-4">
        • At least 8 characters long
      </Description>
      <Description className="text-xs text-gray-500 ml-4">
        • Contains uppercase and lowercase letters
      </Description>
      <Description className="text-xs text-gray-500 ml-4">
        • Contains at least one number
      </Description>
    </Field>
  );
}

// Using description hook
function CustomControlWithDescription() {
  const descriptionId = useDescribedBy();
  
  return (
    <div>
      <label>Custom Slider</label>
      <Description>Adjust the value between 0 and 100</Description>
      
      <div
        role="slider"
        aria-describedby={descriptionId}
        tabIndex={0}
        className="w-64 h-6 bg-gray-200 rounded-full"
      >
        {/* Custom slider implementation */}
      </div>
    </div>
  );
}

Transition

A component for managing CSS transitions with declarative syntax.

/**
 * Component for managing CSS transitions
 * @param props - Transition properties including CSS classes and timing
 */
function Transition<TTag extends keyof JSX.IntrinsicElements = 'div'>(
  props: TransitionProps<TTag>
): JSX.Element;

interface TransitionProps<TTag extends keyof JSX.IntrinsicElements = 'div'> 
  extends PolymorphicProps<TTag> {
  /** Whether to show the content (triggers transition) */
  show?: boolean;
  /** Whether to animate on initial render */
  appear?: boolean;
  /** CSS classes for enter transition */
  enter?: string;
  /** CSS classes for enter start state */
  enterFrom?: string;
  /** CSS classes for enter end state */
  enterTo?: string;
  /** CSS classes for leave transition */
  leave?: string;
  /** CSS classes for leave start state */
  leaveFrom?: string;
  /** CSS classes for leave end state */
  leaveTo?: string;
  /** Callback before enter transition starts */
  beforeEnter?: () => void;
  /** Callback after enter transition completes */
  afterEnter?: () => void;
  /** Callback before leave transition starts */
  beforeLeave?: () => void;
  /** Callback after leave transition completes */
  afterLeave?: () => void;
}

TransitionChild

Individual transition child component for complex transitions.

/**
 * Individual transition child component
 * @param props - TransitionChild properties
 */
function TransitionChild<TTag extends keyof JSX.IntrinsicElements = 'div'>(
  props: TransitionChildProps<TTag>
): JSX.Element;

interface TransitionChildProps<TTag extends keyof JSX.IntrinsicElements = 'div'> 
  extends PolymorphicProps<TTag> {
  /** CSS classes for enter transition */
  enter?: string;
  /** CSS classes for enter start state */
  enterFrom?: string;
  /** CSS classes for enter end state */
  enterTo?: string;
  /** CSS classes for leave transition */
  leave?: string;
  /** CSS classes for leave start state */
  leaveFrom?: string;
  /** CSS classes for leave end state */
  leaveTo?: string;
  /** Callback before enter transition starts */
  beforeEnter?: () => void;
  /** Callback after enter transition completes */
  afterEnter?: () => void;
  /** Callback before leave transition starts */
  beforeLeave?: () => void;
  /** Callback after leave transition completes */
  afterLeave?: () => void;
}

Usage Examples:

import { useState } from "react";
import { Transition, TransitionChild, Button } from "@headlessui/react";

function BasicTransitionExample() {
  const [show, setShow] = useState(false);
  
  return (
    <div>
      <Button onClick={() => setShow(!show)}>
        Toggle
      </Button>
      
      <Transition
        show={show}
        enter="transition-opacity duration-300"
        enterFrom="opacity-0"
        enterTo="opacity-100"
        leave="transition-opacity duration-300"
        leaveFrom="opacity-100"
        leaveTo="opacity-0"
      >
        <div className="bg-blue-500 text-white p-4 rounded-lg mt-4">
          This content fades in and out
        </div>
      </Transition>
    </div>
  );
}

// Complex transition with multiple children
function ComplexTransitionExample() {
  const [show, setShow] = useState(false);
  
  return (
    <div>
      <Button onClick={() => setShow(!show)}>
        Toggle Modal
      </Button>
      
      <Transition show={show}>
        {/* Backdrop */}
        <TransitionChild
          enter="ease-out duration-300"
          enterFrom="opacity-0"
          enterTo="opacity-100"
          leave="ease-in duration-200"  
          leaveFrom="opacity-100"
          leaveTo="opacity-0"
          className="fixed inset-0 bg-black bg-opacity-25"
        />
        
        {/* Modal panel */}
        <div className="fixed inset-0 flex items-center justify-center p-4">
          <TransitionChild
            enter="ease-out duration-300"
            enterFrom="opacity-0 scale-95"
            enterTo="opacity-100 scale-100"
            leave="ease-in duration-200"
            leaveFrom="opacity-100 scale-100"
            leaveTo="opacity-0 scale-95"
            className="bg-white rounded-lg p-6 shadow-xl max-w-md w-full"
          >
            <h3 className="text-lg font-medium mb-4">Modal Title</h3>
            <p className="text-gray-600 mb-4">Modal content goes here...</p>
            <Button onClick={() => setShow(false)}>
              Close
            </Button>
          </TransitionChild>
        </div>
      </Transition>
    </div>
  );
}

// Transition with callbacks
function TransitionWithCallbacksExample() {
  const [show, setShow] = useState(false);
  
  return (
    <div>
      <Button onClick={() => setShow(!show)}>
        Toggle with Callbacks
      </Button>
      
      <Transition
        show={show}
        beforeEnter={() => console.log('About to enter')}
        afterEnter={() => console.log('Enter complete')}
        beforeLeave={() => console.log('About to leave')}
        afterLeave={() => console.log('Leave complete')}
        enter="transition-all duration-500"
        enterFrom="opacity-0 transform scale-50"
        enterTo="opacity-100 transform scale-100"
        leave="transition-all duration-300"
        leaveFrom="opacity-100 transform scale-100"
        leaveTo="opacity-0 transform scale-50"
        className="bg-green-500 text-white p-4 rounded-lg mt-4 origin-center"
      >
        Content with transition callbacks
      </Transition>
    </div>
  );
}

DataInteractive

A wrapper component that provides interaction states without default behavior, useful for custom interactive elements.

/**
 * Wrapper providing interaction states without default behavior
 * @param props - DataInteractive properties
 */
function DataInteractive<TTag extends keyof JSX.IntrinsicElements = 'div'>(
  props: DataInteractiveProps<TTag>
): JSX.Element;

interface DataInteractiveProps<TTag extends keyof JSX.IntrinsicElements = 'div'> 
  extends PolymorphicProps<TTag> {
  /** Render prop providing interaction state */
  children?: React.ReactNode | ((props: DataInteractiveRenderProps) => React.ReactNode);
}

interface DataInteractiveRenderProps {
  /** Whether being hovered */
  hover: boolean;
  /** Whether has focus */
  focus: boolean;
  /** Whether being pressed */
  active: boolean;
}

Usage Examples:

import { DataInteractive } from "@headlessui/react";

function DataInteractiveExample() {
  return (
    <DataInteractive as="div" tabIndex={0}>
      {({ hover, focus, active }) => (
        <div
          className={`
            p-4 rounded-lg cursor-pointer transition-colors
            ${hover ? 'bg-blue-100' : 'bg-gray-100'}
            ${focus ? 'ring-2 ring-blue-500' : ''}
            ${active ? 'bg-blue-200' : ''}
          `}
        >
          Custom interactive element
          <div className="text-sm text-gray-600 mt-2">
            Hover: {hover ? '✓' : '✗'} | 
            Focus: {focus ? '✓' : '✗'} | 
            Active: {active ? '✓' : '✗'}
          </div>
        </div>
      )}
    </DataInteractive>
  );
}

// Custom card with interactive states
function InteractiveCard() {
  return (
    <DataInteractive
      as="article"
      tabIndex={0}
      onClick={() => console.log('Card clicked')}
      onKeyDown={(e) => {
        if (e.key === 'Enter' || e.key === ' ') {
          console.log('Card activated');
        }
      }}
    >
      {({ hover, focus, active }) => (
        <div
          className={`
            border rounded-lg p-6 transition-all duration-200
            ${hover ? 'shadow-lg border-blue-300' : 'shadow border-gray-200'}
            ${focus ? 'ring-2 ring-blue-500 ring-offset-2' : ''}
            ${active ? 'scale-95' : ''}
          `}
        >
          <h3 className="text-lg font-semibold mb-2">Interactive Card</h3>
          <p className="text-gray-600">
            This card responds to hover, focus, and active states.
          </p>
        </div>
      )}
    </DataInteractive>
  );
}

useClose

A hook that provides access to the close function from the nearest overlay component context.

/**
 * Hook providing access to close function from component context
 * @returns Function to close the containing component
 */
function useClose(): () => void;

Usage Examples:

import { Dialog, DialogPanel, useClose } from "@headlessui/react";

function DialogContent() {
  const close = useClose();
  
  return (
    <div className="space-y-4">
      <h2 className="text-xl font-semibold">Dialog Content</h2>
      <p>This component can close the dialog using the useClose hook.</p>
      
      <div className="flex gap-2">
        <button 
          onClick={close}
          className="px-4 py-2 bg-gray-500 text-white rounded hover:bg-gray-600"
        >
          Cancel
        </button>
        <button 
          onClick={() => {
            // Do something then close
            console.log('Action completed');
            close();
          }}
          className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
        >
          Confirm
        </button>
      </div>
    </div>
  );
}

function DialogWithUseClose() {
  const [isOpen, setIsOpen] = useState(false);
  
  return (
    <>
      <button onClick={() => setIsOpen(true)}>Open Dialog</button>
      
      <Dialog open={isOpen} onClose={() => setIsOpen(false)}>
        <div className="fixed inset-0 bg-black/30" aria-hidden="true" />
        <div className="fixed inset-0 flex items-center justify-center p-4">
          <DialogPanel className="bg-white p-6 rounded-lg">
            <DialogContent />
          </DialogPanel>
        </div>
      </Dialog>
    </>
  );
}

// Custom hook using useClose
function useDialogAction() {
  const close = useClose();
  
  const handleSave = async (data: any) => {
    try {
      await saveData(data);
      close(); // Close dialog on successful save
    } catch (error) {
      console.error('Save failed:', error);
      // Don't close on error
    }
  };
  
  return { handleSave };
}

Common Types

// Base polymorphic props for all utility components
interface PolymorphicProps<TTag extends keyof JSX.IntrinsicElements = 'div'> {
  /** Element or component to render as */
  as?: TTag;
  /** Children content or render prop function */
  children?: React.ReactNode | ((props: any) => React.ReactNode);
}

// Focus trap feature flags
enum FocusTrapFeatures {
  None = 0,
  InitialFocus = 1,
  TabLock = 2,
  FocusLock = 4,
  RestoreFocus = 8,
  AutoFocus = 16
}

// Container type for focus trap
type Containers = React.MutableRefObject<Set<React.MutableRefObject<HTMLElement | null>>>;

// Common interaction state
interface InteractionState {
  hover: boolean;
  focus: boolean;
  active: boolean;
}

// Transition callback functions
type TransitionCallback = () => void;

Accessibility Features

Utility components enhance accessibility in several ways:

  • Focus Management: FocusTrap ensures modal dialogs are properly contained
  • Label Association: Label and Description automatically wire up ARIA relationships
  • Portal Rendering: Portal maintains accessibility context across DOM boundaries
  • Keyboard Support: DataInteractive responds to keyboard activation
  • Screen Reader Support: All components work properly with assistive technologies

Integration Patterns

These utilities are designed to work together and with other Headless UI components:

// Common pattern: Dialog with all utilities
<Dialog open={isOpen} onClose={setIsOpen}>
  <Portal>
    <Transition show={isOpen}>
      <FocusTrap>
        <DialogPanel>
          <Label>Dialog Title</Label>
          <Description>Dialog description</Description>
          {/* Content */}
        </DialogPanel>
      </FocusTrap>
    </Transition>
  </Portal>
</Dialog>