or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

coordinates.mdcss-utilities.mdevent-handling.mdexecution-context.mdfocus-management.mdhooks.mdindex.mdmath-operations.mdtype-guards.mdtypescript-utilities.md
tile.json

focus-management.mddocs/

Focus Management

Utilities for finding and managing focusable elements in the DOM, essential for accessibility and keyboard navigation in drag and drop interfaces.

Capabilities

findFirstFocusableNode

Finds the first focusable element within or including the given element. Uses a comprehensive CSS selector to identify elements that can receive keyboard focus.

/**
 * Finds the first focusable element within or including the given element
 * @param element - The container element to search within
 * @returns The first focusable HTMLElement found, or null if none exists
 */
function findFirstFocusableNode(element: HTMLElement): HTMLElement | null;

Focusable Elements Detected:

  • Links (<a> with href)
  • Form controls (<input>, <select>, <textarea>, <button>) that are not disabled
  • Frames and iframes
  • Elements with explicit tabindex attribute

Usage Examples:

import { findFirstFocusableNode } from "@dnd-kit/utilities";

// Basic focus management
function focusFirstElement(container: HTMLElement) {
  const firstFocusable = findFirstFocusableNode(container);
  if (firstFocusable) {
    firstFocusable.focus();
    console.log("Focused:", firstFocusable.tagName);
  } else {
    console.log("No focusable elements found");
  }
}

// Modal dialog focus management
function setupModalFocus(modal: HTMLElement) {
  // Focus first element when modal opens
  const firstFocusable = findFirstFocusableNode(modal);
  if (firstFocusable) {
    firstFocusable.focus();
  }
  
  // Trap focus within modal
  modal.addEventListener("keydown", (event) => {
    if (event.key === "Tab") {
      const focusableElements = getAllFocusableElements(modal);
      const firstElement = focusableElements[0];
      const lastElement = focusableElements[focusableElements.length - 1];
      
      if (event.shiftKey && document.activeElement === firstElement) {
        event.preventDefault();
        lastElement?.focus();
      } else if (!event.shiftKey && document.activeElement === lastElement) {
        event.preventDefault();
        firstElement?.focus();
      }
    }
  });
}

function getAllFocusableElements(container: HTMLElement): HTMLElement[] {
  const selector = 'a,frame,iframe,input:not([type=hidden]):not(:disabled),select:not(:disabled),textarea:not(:disabled),button:not(:disabled),*[tabindex]';
  return Array.from(container.querySelectorAll(selector)) as HTMLElement[];
}

// Drag and drop accessibility
function setupAccessibleDragDrop(draggableContainer: HTMLElement) {
  const firstFocusable = findFirstFocusableNode(draggableContainer);
  
  if (firstFocusable) {
    // Make container keyboard navigable if no focusable children
    draggableContainer.tabIndex = 0;
    draggableContainer.setAttribute("role", "button");
    draggableContainer.setAttribute("aria-label", "Draggable item");
    
    // Handle keyboard drag initiation
    draggableContainer.addEventListener("keydown", (event) => {
      if (event.key === "Enter" || event.key === " ") {
        event.preventDefault();
        initiateDragWithKeyboard(draggableContainer);
      }
    });
  }
}

function initiateDragWithKeyboard(element: HTMLElement) {
  console.log("Starting keyboard drag for:", element);
  // Implement keyboard drag logic
}

// Form validation with focus management
function validateFormWithFocus(form: HTMLFormElement) {
  const inputs = form.querySelectorAll("input, select, textarea") as NodeListOf<HTMLInputElement>;
  
  for (const input of inputs) {
    if (!input.validity.valid) {
      // Focus the first invalid field
      input.focus();
      input.scrollIntoView({ behavior: "smooth", block: "center" });
      
      // Highlight the error
      input.setAttribute("aria-invalid", "true");
      return false;
    }
  }
  
  return true;
}

// React component example
import React, { useEffect, useRef } from "react";

function AccessibleDropZone({ children, onDrop }: {
  children: React.ReactNode;
  onDrop: (data: any) => void;
}) {
  const dropZoneRef = useRef<HTMLDivElement>(null);
  
  useEffect(() => {
    const dropZone = dropZoneRef.current;
    if (!dropZone) return;
    
    // Ensure drop zone is focusable
    const firstFocusable = findFirstFocusableNode(dropZone);
    if (!firstFocusable) {
      dropZone.tabIndex = 0;
      dropZone.setAttribute("role", "region");
      dropZone.setAttribute("aria-label", "Drop zone");
    }
    
    // Handle keyboard interactions
    const handleKeyDown = (event: KeyboardEvent) => {
      if (event.key === "Enter") {
        // Trigger drop action via keyboard
        onDrop({ type: "keyboard-drop" });
      }
    };
    
    dropZone.addEventListener("keydown", handleKeyDown);
    
    return () => {
      dropZone.removeEventListener("keydown", handleKeyDown);
    };
  }, [onDrop]);
  
  return (
    <div
      ref={dropZoneRef}
      className="drop-zone"
      onDragOver={(e) => e.preventDefault()}
      onDrop={(e) => {
        e.preventDefault();
        onDrop({ type: "drag-drop", data: e.dataTransfer?.getData("text") });
      }}
    >
      {children}
    </div>
  );
}

// Dynamic content focus management
function handleDynamicContentFocus(container: HTMLElement, newContent: HTMLElement) {
  // Add new content
  container.appendChild(newContent);
  
  // Focus first focusable element in new content
  const firstFocusable = findFirstFocusableNode(newContent);
  if (firstFocusable) {
    // Announce to screen readers
    firstFocusable.setAttribute("aria-live", "polite");
    firstFocusable.setAttribute("aria-atomic", "true");
    
    // Focus with slight delay to ensure DOM is updated
    setTimeout(() => {
      firstFocusable.focus();
    }, 100);
  }
}

Accessibility Notes:

  • The function respects the natural tab order and ARIA attributes
  • It only considers elements that are actually focusable (not disabled or hidden)
  • Works well with screen readers and keyboard navigation patterns
  • Useful for implementing WCAG 2.1 AA compliance in drag and drop interfaces