CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-chakra-ui--utils

Common utilities and types for Chakra UI providing type checking, object manipulation, DOM utilities, and React helpers

Pending

Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

Overview
Eval results
Files

focus-accessibility.mddocs/

Focus and Accessibility

Comprehensive accessibility utilities for focus management, tabbable elements, and ARIA attributes. These utilities follow WAI-ARIA guidelines and support keyboard navigation patterns essential for building accessible React applications.

Capabilities

Element Focus Detection

Utilities for detecting focusable and tabbable elements.

/**
 * Checks if an element is focusable (can receive focus programmatically)
 * @param element - Element to check
 * @returns true if element can be focused
 */
function isFocusable(element: HTMLElement): boolean;

/**
 * Checks if an element is tabbable (can be reached via Tab key)
 * @param element - Element to check (optional)
 * @returns true if element is tabbable
 */
function isTabbable(element?: HTMLElement | null): boolean;

/**
 * Checks if element has display: none
 * @param element - Element to check
 * @returns true if element has display: none
 */
function hasDisplayNone(element: HTMLElement): boolean;

/**
 * Checks if element has a tabindex attribute
 * @param element - Element to check
 * @returns true if element has tabindex
 */
function hasTabIndex(element: HTMLElement): boolean;

/**
 * Checks if element has a negative tabindex
 * @param element - Element to check
 * @returns true if element has negative tabindex
 */
function hasNegativeTabIndex(element: HTMLElement): boolean;

/**
 * Checks if element has focus-within
 * @param element - Element to check
 * @returns true if element or its children have focus
 */
function hasFocusWithin(element: HTMLElement): boolean;

Usage Examples:

import { isFocusable, isTabbable, hasFocusWithin } from "@chakra-ui/utils";

// Focus management in a modal
function Modal({ children, isOpen }: ModalProps) {
  const modalRef = React.useRef<HTMLDivElement>(null);
  
  React.useEffect(() => {
    if (isOpen && modalRef.current) {
      // Find first focusable element and focus it
      const focusableElements = Array.from(modalRef.current.querySelectorAll("*"))
        .filter((el): el is HTMLElement => el instanceof HTMLElement)
        .filter(isFocusable);
      
      if (focusableElements.length > 0) {
        focusableElements[0].focus();
      }
    }
  }, [isOpen]);
  
  return isOpen ? <div ref={modalRef}>{children}</div> : null;
}

// Custom focus trap
function useFocusTrap(ref: React.RefObject<HTMLElement>) {
  React.useEffect(() => {
    const container = ref.current;
    if (!container) return;
    
    function handleTab(event: KeyboardEvent) {
      if (event.key !== "Tab") return;
      
      const tabbableElements = Array.from(container.querySelectorAll("*"))
        .filter((el): el is HTMLElement => el instanceof HTMLElement)
        .filter(isTabbable);
      
      const firstTabbable = tabbableElements[0];
      const lastTabbable = tabbableElements[tabbableElements.length - 1];
      
      if (event.shiftKey && document.activeElement === firstTabbable) {
        event.preventDefault();
        lastTabbable?.focus();
      } else if (!event.shiftKey && document.activeElement === lastTabbable) {
        event.preventDefault();
        firstTabbable?.focus();
      }
    }
    
    container.addEventListener("keydown", handleTab);
    return () => container.removeEventListener("keydown", handleTab);
  }, [ref]);
}

Focus Query Utilities

Utilities for finding focusable and tabbable elements within containers.

/**
 * Gets all focusable elements within a container
 * @param container - Container element to search within
 * @returns Array of focusable elements
 */
function getAllFocusable<T extends HTMLElement>(container: T): T[];

/**
 * Gets the first focusable element within a container
 * @param container - Container element to search within
 * @returns First focusable element or null
 */
function getFirstFocusable<T extends HTMLElement>(container: T): T | null;

/**
 * Gets all tabbable elements within a container
 * @param container - Container element to search within
 * @param fallbackToFocusable - Whether to fallback to focusable elements if no tabbable found
 * @returns Array of tabbable elements
 */
function getAllTabbable<T extends HTMLElement>(
  container: T, 
  fallbackToFocusable?: boolean
): T[];

/**
 * Gets the first tabbable element within a container
 * @param container - Container element to search within
 * @param fallbackToFocusable - Whether to fallback to focusable elements if no tabbable found
 * @returns First tabbable element or null
 */
function getFirstTabbableIn<T extends HTMLElement>(
  container: T, 
  fallbackToFocusable?: boolean
): T | null;

/**
 * Gets the last tabbable element within a container
 * @param container - Container element to search within
 * @param fallbackToFocusable - Whether to fallback to focusable elements if no tabbable found
 * @returns Last tabbable element or null
 */
function getLastTabbableIn<T extends HTMLElement>(
  container: T, 
  fallbackToFocusable?: boolean
): T | null;

/**
 * Gets the next tabbable element after the currently focused element
 * @param container - Container element to search within
 * @param fallbackToFocusable - Whether to fallback to focusable elements if no tabbable found
 * @returns Next tabbable element or null
 */
function getNextTabbable<T extends HTMLElement>(
  container: T, 
  fallbackToFocusable?: boolean
): T | null;

/**
 * Gets the previous tabbable element before the currently focused element
 * @param container - Container element to search within
 * @param fallbackToFocusable - Whether to fallback to focusable elements if no tabbable found
 * @returns Previous tabbable element or null
 */
function getPreviousTabbable<T extends HTMLElement>(
  container: T, 
  fallbackToFocusable?: boolean
): T | null;

Usage Examples:

import { 
  getAllFocusable, 
  getFirstTabbableIn, 
  getLastTabbableIn,
  getNextTabbable,
  getPreviousTabbable 
} from "@chakra-ui/utils";

// Dropdown with keyboard navigation
function Dropdown({ isOpen, options, onSelect }: DropdownProps) {
  const listRef = React.useRef<HTMLUListElement>(null);
  const [focusedIndex, setFocusedIndex] = React.useState(-1);
  
  React.useEffect(() => {
    if (isOpen && listRef.current) {
      const firstTabbable = getFirstTabbableIn(listRef.current);
      firstTabbable?.focus();
      setFocusedIndex(0);
    }
  }, [isOpen]);
  
  const handleKeyDown = (event: React.KeyboardEvent) => {
    if (!listRef.current) return;
    
    switch (event.key) {
      case "ArrowDown":
        event.preventDefault();
        const nextElement = getNextTabbable(listRef.current);
        if (nextElement) {
          nextElement.focus();
          setFocusedIndex(prev => prev + 1);
        }
        break;
        
      case "ArrowUp":
        event.preventDefault();
        const prevElement = getPreviousTabbable(listRef.current);
        if (prevElement) {
          prevElement.focus();
          setFocusedIndex(prev => prev - 1);
        }
        break;
        
      case "Home":
        event.preventDefault();
        const firstElement = getFirstTabbableIn(listRef.current);
        if (firstElement) {
          firstElement.focus();
          setFocusedIndex(0);
        }
        break;
        
      case "End":
        event.preventDefault();
        const lastElement = getLastTabbableIn(listRef.current);
        if (lastElement) {
          lastElement.focus();
          setFocusedIndex(options.length - 1);
        }
        break;
    }
  };
  
  return (
    <ul ref={listRef} onKeyDown={handleKeyDown}>
      {options.map((option, index) => (
        <li key={option.id} tabIndex={-1} onClick={() => onSelect(option)}>
          {option.label}
        </li>
      ))}
    </ul>
  );
}

// Focus restoration utility
function useFocusRestore() {
  const previouslyFocusedRef = React.useRef<HTMLElement | null>(null);
  
  const saveFocus = React.useCallback(() => {
    previouslyFocusedRef.current = document.activeElement as HTMLElement;
  }, []);
  
  const restoreFocus = React.useCallback(() => {
    const elementToFocus = previouslyFocusedRef.current;
    if (elementToFocus && isFocusable(elementToFocus)) {
      elementToFocus.focus();
    }
  }, []);
  
  return { saveFocus, restoreFocus };
}

Element State Detection

Utilities for detecting various element states relevant to accessibility.

/**
 * Type guard that checks if an element is an HTMLElement
 * @param el - Element to check
 * @returns true if element is HTMLElement
 */
function isHTMLElement(el: any): el is HTMLElement;

/**
 * Checks if running in browser environment
 * @returns true if in browser environment
 */
function isBrowser(): boolean;

/**
 * Type guard that checks if an element is an input element
 * @param element - Focusable element to check
 * @returns true if element is HTMLInputElement
 */
function isInputElement(element: FocusableElement): element is HTMLInputElement;

/**
 * Checks if an element is currently the active element
 * @param element - Focusable element to check
 * @returns true if element is currently focused
 */
function isActiveElement(element: FocusableElement): boolean;

/**
 * Checks if an element is hidden (not visible)
 * @param element - Element to check
 * @returns true if element is hidden
 */
function isHiddenElement(element: HTMLElement): boolean;

/**
 * Checks if an element is content editable
 * @param element - Element to check
 * @returns true if element is content editable
 */
function isContentEditableElement(element: HTMLElement): boolean;

/**
 * Checks if an element is disabled
 * @param element - Element to check
 * @returns true if element is disabled
 */
function isDisabledElement(element: HTMLElement): boolean;

Usage Examples:

import { 
  isHTMLElement, 
  isInputElement, 
  isActiveElement,
  isHiddenElement,
  isDisabledElement 
} from "@chakra-ui/utils";

// Form validation helper
function validateFormElement(element: unknown): string | null {
  if (!isHTMLElement(element)) {
    return "Not a valid HTML element";
  }
  
  if (isHiddenElement(element)) {
    return "Element is hidden";
  }
  
  if (isDisabledElement(element)) {
    return "Element is disabled";
  }
  
  if (isInputElement(element) && element.required && !element.value) {
    return "Required field is empty";
  }
  
  return null;
}

// Focus indicator component
function FocusIndicator({ children }: { children: React.ReactNode }) {
  const [hasFocus, setHasFocus] = React.useState(false);
  const elementRef = React.useRef<HTMLElement>(null);
  
  React.useEffect(() => {
    const checkFocus = () => {
      if (elementRef.current) {
        setHasFocus(isActiveElement(elementRef.current));
      }
    };
    
    document.addEventListener("focusin", checkFocus);
    document.addEventListener("focusout", checkFocus);
    
    return () => {
      document.removeEventListener("focusin", checkFocus);
      document.removeEventListener("focusout", checkFocus);
    };
  }, []);
  
  return (
    <div 
      ref={elementRef as any}
      className={cx("focus-container", hasFocus && "focus-container--focused")}
    >
      {children}
    </div>
  );
}

ARIA Attribute Utilities

Utilities for generating proper ARIA and data attributes.

/**
 * Returns appropriate data attribute value based on condition
 * @param condition - Boolean condition
 * @returns Empty string for true, undefined for false/undefined
 */
function dataAttr(condition: boolean | undefined): Booleanish;

/**
 * Returns appropriate ARIA attribute value based on condition
 * @param condition - Boolean condition
 * @returns true or undefined based on condition
 */
function ariaAttr(condition: boolean | undefined): true | undefined;

type Booleanish = boolean | "true" | "false";

Usage Examples:

import { dataAttr, ariaAttr } from "@chakra-ui/utils";

// Accessible button component
interface ButtonProps {
  children: React.ReactNode;
  isPressed?: boolean;
  isExpanded?: boolean;
  isDisabled?: boolean;
  onClick?: () => void;
}

function AccessibleButton({ 
  children, 
  isPressed, 
  isExpanded, 
  isDisabled,
  onClick 
}: ButtonProps) {
  return (
    <button
      onClick={onClick}
      disabled={isDisabled}
      aria-pressed={ariaAttr(isPressed)}
      aria-expanded={ariaAttr(isExpanded)}
      aria-disabled={ariaAttr(isDisabled)}
      data-pressed={dataAttr(isPressed)}
      data-expanded={dataAttr(isExpanded)}
      data-disabled={dataAttr(isDisabled)}
    >
      {children}
    </button>
  );
}

// Accessible menu component
function Menu({ isOpen, items }: MenuProps) {
  return (
    <div
      role="menu"
      aria-expanded={ariaAttr(isOpen)}
      data-open={dataAttr(isOpen)}
    >
      {items.map((item, index) => (
        <div
          key={item.id}
          role="menuitem"
          tabIndex={-1}
          aria-disabled={ariaAttr(item.disabled)}
          data-disabled={dataAttr(item.disabled)}
          data-selected={dataAttr(item.selected)}
        >
          {item.label}
        </div>
      ))}
    </div>
  );
}

// CSS styling based on data attributes
const styles = `
  .button[data-pressed="true"] {
    background-color: var(--pressed-bg);
  }
  
  .menu[data-open="true"] {
    display: block;
  }
  
  .menu-item[data-disabled="true"] {
    opacity: 0.5;
    pointer-events: none;
  }
`;

Install with Tessl CLI

npx tessl i tessl/npm-chakra-ui--utils

docs

dom-events.md

dom-utilities.md

focus-accessibility.md

function-utilities.md

index.md

math-utilities.md

object-manipulation.md

react-utilities.md

responsive.md

type-checking.md

tile.json