or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

core-positioning.mdfocus-management.mdindex.mdinteraction-hooks.mdlayout-components.mdlist-navigation.mdpositioning-middleware.mdtransitions.mdtree-context.md
tile.json

tree-context.mddocs/

Tree Context & Coordination

System for managing nested floating elements, coordinating behavior across multiple related floating UIs, and managing hierarchical relationships between floating elements like nested menus and complex dropdowns.

Capabilities

FloatingTree Component

Context provider for managing nested floating elements and their hierarchical relationships.

/**
 * Context provider for nested floating element trees
 * @param props - Tree context configuration
 * @returns Tree context provider
 */
interface FloatingTreeProps {
  children?: React.ReactNode;
}

declare const FloatingTree: React.FC<FloatingTreeProps>;

Usage Example:

import { FloatingTree, FloatingNode, useFloating } from '@floating-ui/react';

function NestedMenus() {
  return (
    <FloatingTree>
      <MainMenu />
    </FloatingTree>
  );
}

function MainMenu() {
  const [isOpen, setIsOpen] = useState(false);

  return (
    <>
      <button onClick={() => setIsOpen(!isOpen)}>Main Menu</button>
      {isOpen && (
        <FloatingNode id="main-menu">
          <div>
            <button>Item 1</button>
            <SubMenu />
            <button>Item 3</button>
          </div>
        </FloatingNode>
      )}
    </>
  );
}

function SubMenu() {
  const [isOpen, setIsOpen] = useState(false);

  return (
    <>
      <button onClick={() => setIsOpen(!isOpen)}>Submenu →</button>
      {isOpen && (
        <FloatingNode id="sub-menu">
          <div>
            <button>Sub Item 1</button>
            <button>Sub Item 2</button>
          </div>
        </FloatingNode>
      )}
    </>
  );
}

FloatingNode Component

Individual node in a floating element tree with ID management and parent-child relationships.

/**
 * Individual node in floating element tree
 * @param props - Node configuration
 * @returns Tree node component
 */
interface FloatingNodeProps {
  children?: React.ReactNode;
  id: string;
}

declare const FloatingNode: React.FC<FloatingNodeProps>;

useFloatingTree Hook

Accesses the floating element tree context and provides tree management methods.

/**
 * Access floating element tree context
 * @returns Tree context with events and node management
 */
function useFloatingTree(): FloatingTreeType | null;

interface FloatingTreeType {
  nodesRef: React.MutableRefObject<Map<string, FloatingNodeType>>;
  events: FloatingEvents;
  addNode(node: FloatingNodeType): void;
  removeNode(id: string): void;
}

interface FloatingNodeType {
  id: string;
  parentId: string | null;
  context?: FloatingRootContext;
}

Usage Examples:

import { useFloatingTree, FloatingTree } from '@floating-ui/react';

function TreeManager() {
  const tree = useFloatingTree();

  useEffect(() => {
    if (tree) {
      // Listen for tree events
      tree.events.on('nodeopen', (data) => {
        console.log('Node opened:', data.nodeId);
      });

      tree.events.on('nodeclose', (data) => {
        console.log('Node closed:', data.nodeId);
      });
    }
  }, [tree]);

  return (
    <div>
      <p>Tree nodes: {tree?.nodesRef.current.size || 0}</p>
    </div>
  );
}

// Custom tree management
function CustomTreeNode({ id, children }: { id: string; children: React.ReactNode }) {
  const tree = useFloatingTree();

  useEffect(() => {
    if (tree) {
      const node: FloatingNodeType = {
        id,
        parentId: null, // Set based on context
      };
      tree.addNode(node);

      return () => {
        tree.removeNode(id);
      };
    }
  }, [tree, id]);

  return <>{children}</>;
}

useFloatingNodeId Hook

Gets the current node ID within the floating element tree.

/**
 * Get current node ID in floating tree
 * @returns Current node ID or undefined
 */
function useFloatingNodeId(): string | undefined;

useFloatingParentNodeId Hook

Gets the parent node ID for the current floating element.

/**
 * Get parent node ID in floating tree
 * @returns Parent node ID or null
 */
function useFloatingParentNodeId(): string | null;

Usage Example:

import { useFloatingNodeId, useFloatingParentNodeId } from '@floating-ui/react';

function NodeInfo() {
  const nodeId = useFloatingNodeId();
  const parentId = useFloatingParentNodeId();

  return (
    <div>
      <p>Current node: {nodeId}</p>
      <p>Parent node: {parentId || 'None'}</p>
    </div>
  );
}

Delay Group Coordination

FloatingDelayGroup Component

Coordinates delay behavior across multiple floating elements for smooth user interactions.

/**
 * Coordinates delay behavior across multiple floating elements
 * @param props - Delay group configuration
 * @returns Delay group context provider
 */
interface FloatingDelayGroupProps {
  children?: React.ReactNode;
  delay?: Delay | (() => Delay);
  timeoutMs?: number;
}

declare const FloatingDelayGroup: React.FC<FloatingDelayGroupProps>;

Usage Example:

import { 
  FloatingDelayGroup, 
  useDelayGroup,
  useFloating, 
  useHover, 
  useInteractions 
} from '@floating-ui/react';

function CoordinatedTooltips() {
  return (
    <FloatingDelayGroup delay={{ open: 1000, close: 200 }}>
      <TooltipItem label="First tooltip">
        <button>Hover me</button>
      </TooltipItem>
      <TooltipItem label="Second tooltip">
        <button>Then me</button>
      </TooltipItem>
      <TooltipItem label="Third tooltip">
        <button>Finally me</button>
      </TooltipItem>
    </FloatingDelayGroup>
  );
}

function TooltipItem({ 
  children, 
  label 
}: { 
  children: React.ReactElement; 
  label: string;
}) {
  const [isOpen, setIsOpen] = useState(false);

  const { refs, floatingStyles, context } = useFloating({
    open: isOpen,
    onOpenChange: setIsOpen,
  });

  const { delay } = useDelayGroup(context, {
    id: label,
  });

  const hover = useHover(context, {
    delay,
  });

  const { getReferenceProps, getFloatingProps } = useInteractions([hover]);

  return (
    <>
      {React.cloneElement(children, {
        ref: refs.setReference,
        ...getReferenceProps(),
      })}
      {isOpen && (
        <div
          ref={refs.setFloating}
          style={{
            ...floatingStyles,
            background: 'black',
            color: 'white',
            padding: '4px 8px',
            borderRadius: '4px',
            fontSize: '12px',
          }}
          {...getFloatingProps()}
        >
          {label}
        </div>
      )}
    </>
  );
}

NextFloatingDelayGroup Component

Experimental improved delay group with enhanced coordination features.

/**
 * Experimental improved delay group coordination
 * @param props - Next-generation delay group configuration
 * @returns Enhanced delay group context provider
 */
interface NextFloatingDelayGroupProps {
  children?: React.ReactNode;
  delay?: Delay | (() => Delay);
}

declare const NextFloatingDelayGroup: React.FC<NextFloatingDelayGroupProps>;

useDelayGroup Hook

Accesses delay group context and provides coordinated delay functionality.

/**
 * Access delay group functionality and coordination
 * @param context - Floating UI context
 * @param props - Delay group item configuration
 * @returns Delay group state and delay values
 */
function useDelayGroup(
  context: FloatingRootContext,
  props: UseDelayGroupProps
): {
  delay: Delay;
  isInstantPhase: boolean;
  currentId: React.MutableRefObject<string | null>;
};

interface UseDelayGroupProps {
  id: string;
}

useDelayGroupContext Hook (Deprecated)

Legacy hook for accessing delay group context.

/**
 * Access delay group context (deprecated - use useDelayGroup)
 * @returns Delay group context or null
 */
function useDelayGroupContext(): {
  delay: Delay;
  timeoutMs: number;
} | null;

useNextDelayGroup Hook

Experimental hook for enhanced delay group coordination.

/**
 * Experimental enhanced delay group hook
 * @param context - Floating UI context
 * @param props - Next delay group configuration
 * @returns Enhanced delay group state
 */
function useNextDelayGroup(
  context: FloatingRootContext,
  props: UseNextDelayGroupProps
): {
  delay: Delay;
};

interface UseNextDelayGroupProps {
  id: string;
}

Tree Coordination Patterns

Nested Menus

<FloatingTree>
  <FloatingNode id="main">
    <MainMenu>
      <FloatingNode id="sub1">
        <SubMenu />
      </FloatingNode>
    </MainMenu>
  </FloatingNode>
</FloatingTree>

Context Menus

<FloatingTree>
  <ContextMenu>
    <FloatingNode id="context">
      <MenuItem>
        <FloatingNode id="submenu">
          <SubContextMenu />
        </FloatingNode>
      </MenuItem>
    </FloatingNode>
  </ContextMenu>
</FloatingTree>

Coordinated Tooltips

<FloatingDelayGroup delay={{ open: 500, close: 100 }}>
  <TooltipButton id="btn1" />
  <TooltipButton id="btn2" />
  <TooltipButton id="btn3" />
</FloatingDelayGroup>

Event Coordination

Tree Events

The tree context provides event coordination:

  • nodeopen: When a tree node opens
  • nodeclose: When a tree node closes
  • nodenavigation: When navigating between nodes
  • treefocus: When focus moves within the tree

Delay Events

Delay groups coordinate timing:

  • delaystart: When delay period begins
  • delayend: When delay period completes
  • instantphase: When in instant open/close mode

Advanced Tree Management

Parent-Child Relationships

function MenuWithSubmenu() {
  const nodeId = useFloatingNodeId();
  const parentId = useFloatingParentNodeId();
  const tree = useFloatingTree();

  useEffect(() => {
    // Custom logic based on tree position
    if (tree && nodeId) {
      const node = tree.nodesRef.current.get(nodeId);
      if (node?.parentId) {
        // Handle parent-child coordination
      }
    }
  }, [tree, nodeId]);

  return (
    <FloatingNode id="submenu">
      {/* Submenu content */}
    </FloatingNode>
  );
}

Tree State Management

function TreeStateManager() {
  const tree = useFloatingTree();
  const [openNodes, setOpenNodes] = useState<Set<string>>(new Set());

  useEffect(() => {
    if (tree) {
      tree.events.on('nodeopen', ({ nodeId }) => {
        setOpenNodes(prev => new Set(prev).add(nodeId));
      });

      tree.events.on('nodeclose', ({ nodeId }) => {
        setOpenNodes(prev => {
          const next = new Set(prev);
          next.delete(nodeId);
          return next;
        });
      });
    }
  }, [tree]);

  return (
    <div>
      <p>Open nodes: {Array.from(openNodes).join(', ')}</p>
    </div>
  );
}