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

navigation-components.mddocs/

Navigation Components

Components for organizing and navigating content including dropdown menus, tab interfaces, and collapsible disclosure panels.

Capabilities

Menu

A dropdown menu component for navigation and actions with keyboard navigation and accessibility.

/**
 * Dropdown menu component for actions and navigation
 * @param props - Menu properties
 */
function Menu<TTag extends keyof JSX.IntrinsicElements = 'div'>(
  props: MenuProps<TTag>
): JSX.Element;

interface MenuProps<TTag extends keyof JSX.IntrinsicElements = 'div'> 
  extends PolymorphicProps<TTag> {
  /** Demo mode flag for development */
  __demoMode?: boolean;
  /** Render prop providing menu state */
  children?: React.ReactNode | ((props: MenuRenderProps) => React.ReactNode);
}

interface MenuRenderProps {
  /** Whether menu is open */
  open: boolean;
  /** Function to close menu */
  close: () => void;
}

MenuButton

Button to toggle the menu display.

/**
 * Button to toggle the menu
 * @param props - MenuButton properties
 */
function MenuButton<TTag extends keyof JSX.IntrinsicElements = 'button'>(
  props: MenuButtonProps<TTag>
): JSX.Element;

interface MenuButtonProps<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: MenuButtonRenderProps) => React.ReactNode);
}

interface MenuButtonRenderProps {
  /** Whether menu 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;
}

MenuItems

Container for menu items with keyboard navigation.

/**
 * Container for menu items
 * @param props - MenuItems properties
 */
function MenuItems<TTag extends keyof JSX.IntrinsicElements = 'div'>(
  props: MenuItemsProps<TTag>
): JSX.Element;

interface MenuItemsProps<TTag extends keyof JSX.IntrinsicElements = 'div'> 
  extends PolymorphicProps<TTag> {
  /** Whether to hold focus on items */
  hold?: 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 items state */
  children?: React.ReactNode | ((props: MenuItemsRenderProps) => React.ReactNode);
}

interface MenuItemsRenderProps {
  /** Whether menu is open */
  open: boolean;
}

MenuItem

Individual item within the menu.

/**
 * Individual item within the menu
 * @param props - MenuItem properties
 */
function MenuItem<TTag extends keyof JSX.IntrinsicElements = 'div'>(
  props: MenuItemProps<TTag>
): JSX.Element;

interface MenuItemProps<TTag extends keyof JSX.IntrinsicElements = 'div'> 
  extends PolymorphicProps<TTag> {
  /** Whether item is disabled */
  disabled?: boolean;
  /** Display order */
  order?: number;
  /** Render prop providing item state */
  children?: React.ReactNode | ((props: MenuItemRenderProps) => React.ReactNode);
}

interface MenuItemRenderProps {
  /** Whether item has focus */
  focus: boolean;
  /** Whether item is active (deprecated, use focus) */
  active: boolean;
  /** Whether item is disabled */
  disabled: boolean;
  /** Function to close menu */
  close: () => void;
}

MenuHeading

Heading component for menu sections.

/**
 * Heading for menu sections
 * @param props - MenuHeading properties
 */
function MenuHeading<TTag extends keyof JSX.IntrinsicElements = 'div'>(
  props: MenuHeadingProps<TTag>
): JSX.Element;

interface MenuHeadingProps<TTag extends keyof JSX.IntrinsicElements = 'div'> 
  extends PolymorphicProps<TTag> {
  /** Standard heading properties */
}

MenuSection

Section grouping component for menu items.

/**
 * Section grouping for menu items
 * @param props - MenuSection properties
 */
function MenuSection<TTag extends keyof JSX.IntrinsicElements = 'div'>(
  props: MenuSectionProps<TTag>
): JSX.Element;

interface MenuSectionProps<TTag extends keyof JSX.IntrinsicElements = 'div'> 
  extends PolymorphicProps<TTag> {
  /** Standard section properties */
}

MenuSeparator

Visual separator between menu items or sections.

/**
 * Visual separator between menu items
 * @param props - MenuSeparator properties
 */
function MenuSeparator<TTag extends keyof JSX.IntrinsicElements = 'div'>(
  props: MenuSeparatorProps<TTag>
): JSX.Element;

interface MenuSeparatorProps<TTag extends keyof JSX.IntrinsicElements = 'div'> 
  extends PolymorphicProps<TTag> {
  /** Standard separator properties */
}

Usage Examples:

import { 
  Menu, 
  MenuButton, 
  MenuItems, 
  MenuItem,
  MenuHeading,
  MenuSection,
  MenuSeparator
} from "@headlessui/react";

function BasicMenuExample() {
  return (
    <Menu>
      <MenuButton className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600">
        Options
      </MenuButton>

      <MenuItems 
        anchor="bottom end"
        className="w-52 bg-white border border-gray-200 rounded-lg shadow-lg p-1 mt-1"
      >
        <MenuItem>
          {({ focus }) => (
            <button
              className={`${focus ? 'bg-blue-500 text-white' : 'text-gray-900'}
                         group flex w-full items-center rounded-md px-2 py-2 text-sm`}
            >
              Edit
            </button>
          )}
        </MenuItem>
        
        <MenuItem>
          {({ focus }) => (
            <button
              className={`${focus ? 'bg-blue-500 text-white' : 'text-gray-900'}
                         group flex w-full items-center rounded-md px-2 py-2 text-sm`}
            >
              Duplicate
            </button>
          )}
        </MenuItem>
        
        <MenuSeparator className="my-1 h-px bg-gray-200" />
        
        <MenuItem disabled>
          {({ focus, disabled }) => (
            <button
              className={`${disabled ? 'text-gray-400' : focus ? 'bg-red-500 text-white' : 'text-red-600'}
                         group flex w-full items-center rounded-md px-2 py-2 text-sm`}
            >
              Delete (disabled)
            </button>
          )}
        </MenuItem>
      </MenuItems>
    </Menu>
  );
}

// Complex menu with sections and headings
function ComplexMenuExample() {
  return (
    <Menu>
      <MenuButton className="flex items-center space-x-2 px-4 py-2 border border-gray-300 rounded">
        <img className="w-8 h-8 rounded-full" src="/profile.jpg" alt="Profile" />
        <span>John Doe</span>
        <ChevronDownIcon className="w-4 h-4" />
      </MenuButton>

      <MenuItems 
        anchor="bottom end"
        className="w-64 bg-white border border-gray-200 rounded-lg shadow-lg p-2 mt-2"
      >
        <MenuSection>
          <MenuHeading className="px-3 py-2 text-xs font-semibold text-gray-500 uppercase tracking-wider">
            Account
          </MenuHeading>
          
          <MenuItem>
            {({ focus, close }) => (
              <a
                href="/profile"
                onClick={() => close()}
                className={`${focus ? 'bg-gray-100' : ''}
                           flex items-center px-3 py-2 text-sm text-gray-700 rounded-md`}
              >
                <UserIcon className="w-4 h-4 mr-3" />
                Your Profile
              </a>
            )}
          </MenuItem>
          
          <MenuItem>
            {({ focus, close }) => (
              <a
                href="/settings"
                onClick={() => close()}
                className={`${focus ? 'bg-gray-100' : ''}
                           flex items-center px-3 py-2 text-sm text-gray-700 rounded-md`}
              >
                <CogIcon className="w-4 h-4 mr-3" />
                Settings
              </a>
            )}
          </MenuItem>
        </MenuSection>

        <MenuSeparator className="my-2 h-px bg-gray-200" />

        <MenuSection>
          <MenuHeading className="px-3 py-2 text-xs font-semibold text-gray-500 uppercase tracking-wider">
            Actions
          </MenuHeading>
          
          <MenuItem>
            {({ focus, close }) => (
              <button
                onClick={() => {
                  console.log('Signing out...');
                  close();
                }}
                className={`${focus ? 'bg-red-50 text-red-700' : 'text-red-600'}
                           flex items-center w-full px-3 py-2 text-sm rounded-md`}
              >
                <ArrowRightOnRectangleIcon className="w-4 h-4 mr-3" />
                Sign out
              </button>
            )}
          </MenuItem>
        </MenuSection>
      </MenuItems>
    </Menu>
  );
}

TabGroup (Tabs)

A tab interface component for organizing content into multiple panels.

/**
 * Tab interface for organizing content
 * @param props - TabGroup properties
 */
function TabGroup<TTag extends keyof JSX.IntrinsicElements = 'div'>(
  props: TabGroupProps<TTag>
): JSX.Element;

interface TabGroupProps<TTag extends keyof JSX.IntrinsicElements = 'div'> 
  extends PolymorphicProps<TTag> {
  /** Currently selected tab index */
  selectedIndex?: number;
  /** Default selected index for uncontrolled usage */
  defaultIndex?: number;
  /** Selection change handler */
  onChange?: (index: number) => void;
  /** Whether tabs are vertically oriented */
  vertical?: boolean;
  /** Whether tab selection requires manual activation (not automatic on focus) */
  manual?: boolean;
  /** Render prop providing tab group state */
  children?: React.ReactNode | ((props: TabGroupRenderProps) => React.ReactNode);
}

interface TabGroupRenderProps {
  /** Currently selected tab index */
  selectedIndex: number;
}

TabList

Container for tab buttons.

/**
 * Container for tab buttons
 * @param props - TabList properties
 */
function TabList<TTag extends keyof JSX.IntrinsicElements = 'div'>(
  props: TabListProps<TTag>
): JSX.Element;

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

interface TabListRenderProps {
  /** Currently selected tab index */
  selectedIndex: number;
}

Tab

Individual tab button.

/**
 * Individual tab button
 * @param props - Tab properties
 */
function Tab<TTag extends keyof JSX.IntrinsicElements = 'button'>(
  props: TabProps<TTag>
): JSX.Element;

interface TabProps<TTag extends keyof JSX.IntrinsicElements = 'button'> 
  extends PolymorphicProps<TTag> {
  /** Whether tab is disabled */
  disabled?: boolean;
  /** Whether to auto-focus on mount */
  autoFocus?: boolean;
  /** Render prop providing tab state */
  children?: React.ReactNode | ((props: TabRenderProps) => React.ReactNode);
}

interface TabRenderProps {
  /** Whether tab is selected */
  selected: boolean;
  /** Whether being hovered */
  hover: boolean;
  /** Whether has focus */
  focus: boolean;
  /** Whether being pressed */
  active: boolean;
  /** Whether has autofocus */
  autofocus: boolean;
  /** Whether disabled */
  disabled: boolean;
}

TabPanels

Container for tab panel content.

/**
 * Container for tab panels
 * @param props - TabPanels properties
 */
function TabPanels<TTag extends keyof JSX.IntrinsicElements = 'div'>(
  props: TabPanelsProps<TTag>
): JSX.Element;

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

interface TabPanelsRenderProps {
  /** Currently selected tab index */
  selectedIndex: number;
}

TabPanel

Individual tab panel content.

/**
 * Individual tab panel content
 * @param props - TabPanel properties
 */
function TabPanel<TTag extends keyof JSX.IntrinsicElements = 'div'>(
  props: TabPanelProps<TTag>
): JSX.Element;

interface TabPanelProps<TTag extends keyof JSX.IntrinsicElements = 'div'> 
  extends PolymorphicProps<TTag> {
  /** Tab index for keyboard navigation */
  tabIndex?: number;
  /** Render prop providing panel state */
  children?: React.ReactNode | ((props: TabPanelRenderProps) => React.ReactNode);
}

interface TabPanelRenderProps {
  /** Whether panel is selected/visible */
  selected: boolean;
  /** Whether panel has focus */
  focus: boolean;
}

Usage Examples:

import { useState } from "react";
import { TabGroup, TabList, Tab, TabPanels, TabPanel } from "@headlessui/react";

function BasicTabsExample() {
  const categories = ['Recent', 'Popular', 'Trending'];

  return (
    <div className="w-full max-w-md mx-auto">
      <TabGroup>
        <TabList className="flex space-x-1 rounded-xl bg-blue-900/20 p-1">
          {categories.map((category) => (
            <Tab
              key={category}
              className={({ selected }) =>
                `w-full rounded-lg py-2.5 text-sm font-medium leading-5
                 ring-white/60 ring-offset-2 ring-offset-blue-400 focus:outline-none focus:ring-2
                 ${selected
                   ? 'bg-white text-blue-700 shadow'
                   : 'text-blue-100 hover:bg-white/[0.12] hover:text-white'
                 }`
              }
            >
              {category}
            </Tab>
          ))}
        </TabList>
        
        <TabPanels className="mt-2">
          <TabPanel className="rounded-xl bg-white p-3">
            <h3 className="text-lg font-semibold">Recent Posts</h3>
            <p className="mt-2 text-gray-600">Content for recent posts...</p>
          </TabPanel>
          
          <TabPanel className="rounded-xl bg-white p-3">
            <h3 className="text-lg font-semibold">Popular Posts</h3>
            <p className="mt-2 text-gray-600">Content for popular posts...</p>
          </TabPanel>
          
          <TabPanel className="rounded-xl bg-white p-3">
            <h3 className="text-lg font-semibold">Trending Posts</h3>
            <p className="mt-2 text-gray-600">Content for trending posts...</p>
          </TabPanel>
        </TabPanels>
      </TabGroup>
    </div>
  );
}

// Controlled tabs
function ControlledTabsExample() {
  const [selectedIndex, setSelectedIndex] = useState(0);
  
  return (
    <TabGroup selectedIndex={selectedIndex} onChange={setSelectedIndex}>
      <TabList className="flex border-b border-gray-200">
        <Tab className={({ selected }) => 
          `px-4 py-2 border-b-2 ${selected ? 'border-blue-500 text-blue-600' : 'border-transparent text-gray-500'}`
        }>
          Profile
        </Tab>
        <Tab className={({ selected }) => 
          `px-4 py-2 border-b-2 ${selected ? 'border-blue-500 text-blue-600' : 'border-transparent text-gray-500'}`
        }>
          Settings
        </Tab>
      </TabList>
      
      <TabPanels>
        <TabPanel className="p-4">
          <p>Selected tab: {selectedIndex}</p>
          <p>Profile content goes here...</p>
        </TabPanel>
        <TabPanel className="p-4">
          <p>Settings content goes here...</p>
        </TabPanel>
      </TabPanels>
    </TabGroup>
  );
}

// Vertical tabs
function VerticalTabsExample() {
  return (
    <TabGroup vertical className="flex">
      <TabList className="flex flex-col w-48 border-r border-gray-200">
        <Tab className={({ selected }) => 
          `px-4 py-3 text-left ${selected ? 'bg-blue-50 border-r-2 border-blue-500 text-blue-700' : 'text-gray-600 hover:bg-gray-50'}`
        }>
          General
        </Tab>
        <Tab className={({ selected }) => 
          `px-4 py-3 text-left ${selected ? 'bg-blue-50 border-r-2 border-blue-500 text-blue-700' : 'text-gray-600 hover:bg-gray-50'}`
        }>
          Security
        </Tab>
        <Tab className={({ selected }) => 
          `px-4 py-3 text-left ${selected ? 'bg-blue-50 border-r-2 border-blue-500 text-blue-700' : 'text-gray-600 hover:bg-gray-50'}`
        }>
          Notifications
        </Tab>
      </TabList>
      
      <TabPanels className="flex-1 p-6">
        <TabPanel>
          <h2 className="text-xl font-semibold mb-4">General Settings</h2>
          <p>General settings content...</p>
        </TabPanel>
        <TabPanel>
          <h2 className="text-xl font-semibold mb-4">Security Settings</h2>
          <p>Security settings content...</p>
        </TabPanel>
        <TabPanel>
          <h2 className="text-xl font-semibold mb-4">Notification Settings</h2>
          <p>Notification settings content...</p>
        </TabPanel>
      </TabPanels>
    </TabGroup>
  );
}

Disclosure

A collapsible disclosure component for showing and hiding content.

/**
 * Collapsible disclosure component
 * @param props - Disclosure properties
 */
function Disclosure<TTag extends keyof JSX.IntrinsicElements = 'div'>(
  props: DisclosureProps<TTag>
): JSX.Element;

interface DisclosureProps<TTag extends keyof JSX.IntrinsicElements = 'div'> 
  extends PolymorphicProps<TTag> {
  /** Default open state for uncontrolled usage */
  defaultOpen?: boolean;
  /** Render prop providing disclosure state */
  children?: React.ReactNode | ((props: DisclosureRenderProps) => React.ReactNode);
}

interface DisclosureRenderProps {
  /** Whether disclosure is open */
  open: boolean;
  /** Function to close disclosure */
  close: (focusableElement?: HTMLElement | React.MutableRefObject<HTMLElement | null>) => void;
}

DisclosureButton

Button to toggle the disclosure state.

/**
 * Button to toggle the disclosure
 * @param props - DisclosureButton properties
 */
function DisclosureButton<TTag extends keyof JSX.IntrinsicElements = 'button'>(
  props: DisclosureButtonProps<TTag>
): JSX.Element;

interface DisclosureButtonProps<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: DisclosureButtonRenderProps) => React.ReactNode);
}

interface DisclosureButtonRenderProps {
  /** Whether disclosure is open */
  open: boolean;
  /** Whether being hovered */
  hover: boolean;
  /** Whether being pressed */
  active: boolean;
  /** Whether disabled */
  disabled: boolean;
  /** Whether has focus */
  focus: boolean;
  /** Whether has autofocus */
  autofocus: boolean;
}

DisclosurePanel

The collapsible content panel.

/**
 * Collapsible content panel
 * @param props - DisclosurePanel properties
 */
function DisclosurePanel<TTag extends keyof JSX.IntrinsicElements = 'div'>(
  props: DisclosurePanelProps<TTag>
): JSX.Element;

interface DisclosurePanelProps<TTag extends keyof JSX.IntrinsicElements = 'div'> 
  extends PolymorphicProps<TTag> {
  /** Whether to use transition animations */
  transition?: boolean;
  /** Render prop providing panel state */
  children?: React.ReactNode | ((props: DisclosurePanelRenderProps) => React.ReactNode);
}

interface DisclosurePanelRenderProps {
  /** Whether disclosure is open */
  open: boolean;
  /** Function to close disclosure */
  close: (focusableElement?: HTMLElement | React.MutableRefObject<HTMLElement | null>) => void;
}

Usage Examples:

import { Disclosure, DisclosureButton, DisclosurePanel } from "@headlessui/react";

function BasicDisclosureExample() {
  return (
    <Disclosure>
      <DisclosureButton className="flex w-full justify-between rounded-lg bg-purple-100 px-4 py-2 text-left text-sm font-medium text-purple-900 hover:bg-purple-200 focus:outline-none focus-visible:ring focus-visible:ring-purple-500/75">
        {({ open }) => (
          <>
            <span>What is your refund policy?</span>
            <ChevronUpIcon
              className={`${open ? 'rotate-180' : ''} h-5 w-5 text-purple-500 transition-transform`}
            />
          </>
        )}
      </DisclosureButton>
      
      <DisclosurePanel className="px-4 pb-2 pt-4 text-sm text-gray-500">
        If you're unhappy with your purchase for any reason, email us within 90 days and we'll refund you in full, no questions asked.
      </DisclosurePanel>
    </Disclosure>
  );
}

// FAQ with multiple disclosures
function FAQExample() {
  const faqs = [
    {
      question: "What's the best thing about Switzerland?",
      answer: "I don't know, but the flag is a big plus. Lorem ipsum dolor sit amet consectetur adipisicing elit."
    },
    {
      question: "How do you make holy water?",
      answer: "You boil the hell out of it. Lorem ipsum dolor sit amet consectetur adipisicing elit."
    },
    {
      question: "Why do you never see elephants hiding in trees?",
      answer: "Because they're so good at it. Lorem ipsum dolor sit amet consectetur adipisicing elit."
    }
  ];

  return (
    <div className="mx-auto w-full max-w-md rounded-2xl bg-white p-2">
      {faqs.map((faq, index) => (
        <Disclosure key={index} as="div" className="mt-2">
          <DisclosureButton className="flex w-full justify-between rounded-lg bg-purple-100 px-4 py-2 text-left text-sm font-medium text-purple-900 hover:bg-purple-200 focus:outline-none focus-visible:ring focus-visible:ring-purple-500/75">
            <span>{faq.question}</span>
            <ChevronUpIcon className="h-5 w-5 text-purple-500 ui-open:rotate-180 ui-open:transform" />
          </DisclosureButton>
          
          <DisclosurePanel className="px-4 pb-2 pt-4 text-sm text-gray-500">
            {faq.answer}
          </DisclosurePanel>
        </Disclosure>
      ))}
    </div>
  );
}

// Disclosure with transitions
function AnimatedDisclosureExample() {
  return (
    <Disclosure>
      <DisclosureButton className="group flex w-full items-center justify-between rounded-lg bg-gray-100 px-4 py-2">
        <span className="text-sm font-medium">Animated Disclosure</span>
        <ChevronDownIcon className="w-5 h-5 group-data-[open]:rotate-180 transition-transform" />
      </DisclosureButton>
      
      <DisclosurePanel 
        transition
        className="origin-top transition duration-200 ease-out data-[closed]:-translate-y-6 data-[closed]:opacity-0"
      >
        <div className="px-4 py-2 text-sm text-gray-600">
          This content animates in and out smoothly when the disclosure is toggled.
        </div>
      </DisclosurePanel>
    </Disclosure>
  );
}

Common Types

// Floating UI anchor configuration for menu positioning
interface AnchorProps {
  to?: string;
  gap?: number;
  offset?: number;
  padding?: number;
}

// Common navigation render props
interface BaseNavigationRenderProps {
  open: boolean;
  close: () => void;
}

// Tab selection change handler
type TabChangeHandler = (index: number) => void;

// Close function with optional focus target
type CloseFunction = (
  focusableElement?: HTMLElement | React.MutableRefObject<HTMLElement | null>
) => void;

Keyboard Navigation

All navigation components include full keyboard support:

  • Arrow Keys: Navigate between tabs, menu items, etc.
  • Enter/Space: Activate items and toggle states
  • Escape: Close menus and disclosures
  • Home/End: Jump to first/last items
  • Tab: Standard tab navigation between components

ARIA Support

Components automatically include appropriate ARIA attributes:

  • role: Proper roles for menus, tabs, disclosures
  • aria-expanded: Indicates open/closed state
  • aria-selected: Shows selected tabs
  • aria-controls: Associates triggers with content
  • aria-labelledby: Links content to labels