CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-react-aria--utils

Essential utility functions and React hooks for building accessible React Aria UI components

Pending

Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

Overview
Eval results
Files

virtual-events-and-input.mddocs/

Virtual Events & Input

Detection and handling of virtual events from assistive technology, keyboard navigation, and platform-specific input methods.

Capabilities

Virtual Click Detection

Functions for detecting clicks that originate from assistive technology or keyboard activation.

/**
 * Detects clicks from keyboard or assistive technology
 * @param event - MouseEvent or PointerEvent to check
 * @returns true if click is from keyboard/AT, false for actual mouse clicks
 */
function isVirtualClick(event: MouseEvent | PointerEvent): boolean;

/**
 * Detects pointer events from assistive technology
 * @param event - PointerEvent to check
 * @returns true if pointer event is from assistive technology
 */
function isVirtualPointerEvent(event: PointerEvent): boolean;

Usage Examples:

import { isVirtualClick, isVirtualPointerEvent } from "@react-aria/utils";

function AccessibleButton({ onClick, children, ...props }) {
  const handleClick = (e: MouseEvent) => {
    const isVirtual = isVirtualClick(e);
    
    console.log(isVirtual ? 'Keyboard/AT activation' : 'Mouse click');
    
    // Different behavior for virtual vs real clicks
    if (isVirtual) {
      // Keyboard activation - provide more feedback
      announceToScreenReader('Button activated');
    }
    
    onClick?.(e);
  };
  
  const handlePointerDown = (e: PointerEvent) => {
    if (isVirtualPointerEvent(e)) {
      // This is from assistive technology
      console.log('AT pointer event');
      e.preventDefault(); // Prevent default AT behavior if needed
    }
  };
  
  return (
    <button 
      onClick={handleClick}
      onPointerDown={handlePointerDown}
      {...props}
    >
      {children}
    </button>
  );
}

// Link component with virtual click handling
function SmartLink({ href, onClick, children, ...props }) {
  const handleClick = (e: MouseEvent) => {
    const isVirtual = isVirtualClick(e);
    
    if (isVirtual) {
      // Keyboard activation of link
      // Don't show loading states that depend on hover
      console.log('Link activated via keyboard');
    } else {
      // Actual mouse click
      // Safe to show hover-dependent UI
      console.log('Link clicked with mouse');
    }
    
    onClick?.(e);
  };
  
  return (
    <a href={href} onClick={handleClick} {...props}>
      {children}
    </a>
  );
}

// Dropdown menu with virtual click awareness
function DropdownMenu({ trigger, items, onSelect }) {
  const [isOpen, setIsOpen] = useState(false);
  
  const handleTriggerClick = (e: MouseEvent) => {
    const isVirtual = isVirtualClick(e);
    
    if (isVirtual) {
      // Keyboard activation - always open menu
      setIsOpen(true);
    } else {
      // Mouse click - toggle menu
      setIsOpen(!isOpen);
    }
  };
  
  const handleItemClick = (item: any, e: MouseEvent) => {
    const isVirtual = isVirtualClick(e);
    
    if (isVirtual) {
      // Keyboard selection - provide confirmation
      announceToScreenReader(`Selected ${item.name}`);
    }
    
    onSelect(item);
    setIsOpen(false);
  };
  
  return (
    <div className="dropdown">
      <button onClick={handleTriggerClick}>
        {trigger}
      </button>
      
      {isOpen && (
        <ul className="dropdown-menu">
          {items.map(item => (
            <li key={item.id}>
              <button onClick={(e) => handleItemClick(item, e)}>
                {item.name}
              </button>
            </li>
          ))}
        </ul>
      )}
    </div>
  );
}

Cross-Platform Key Detection

Function for detecting Ctrl/Cmd key presses in a cross-platform manner.

/**
 * Cross-platform Ctrl/Cmd key detection
 * @param e - Event with modifier key properties
 * @returns true if the platform's primary modifier key is pressed
 */
function isCtrlKeyPressed(e: KeyboardEvent | MouseEvent | PointerEvent): boolean;

Usage Examples:

import { isCtrlKeyPressed } from "@react-aria/utils";

function TextEditor({ content, onChange }) {
  const handleKeyDown = (e: KeyboardEvent) => {
    const isCtrlCmd = isCtrlKeyPressed(e);
    
    if (isCtrlCmd) {
      switch (e.key.toLowerCase()) {
        case 's':
          e.preventDefault();
          saveDocument();
          break;
          
        case 'z':
          e.preventDefault();
          if (e.shiftKey) {
            redo();
          } else {
            undo();
          }
          break;
          
        case 'c':
          // Let default copy behavior work
          console.log('Copy command');
          break;
          
        case 'v':
          // Let default paste behavior work
          console.log('Paste command');
          break;
      }
    }
  };
  
  return (
    <textarea
      value={content}
      onChange={(e) => onChange(e.target.value)}
      onKeyDown={handleKeyDown}
      placeholder="Type here... Use Ctrl/Cmd+S to save, Ctrl/Cmd+Z to undo"
    />
  );
}

// Context menu with keyboard shortcuts
function ContextMenu({ x, y, onClose, onAction }) {
  const menuItems = [
    { id: 'copy', label: 'Copy', shortcut: 'Ctrl+C', action: 'copy' },
    { id: 'paste', label: 'Paste', shortcut: 'Ctrl+V', action: 'paste' },
    { id: 'delete', label: 'Delete', shortcut: 'Del', action: 'delete' }
  ];
  
  const handleGlobalKeyDown = useCallback((e: KeyboardEvent) => {
    const isCtrlCmd = isCtrlKeyPressed(e);
    
    // Handle shortcuts when menu is open
    if (isCtrlCmd) {
      let actionToTrigger = null;
      
      switch (e.key.toLowerCase()) {
        case 'c':
          actionToTrigger = 'copy';
          break;
        case 'v':
          actionToTrigger = 'paste';
          break;
      }
      
      if (actionToTrigger) {
        e.preventDefault();
        onAction(actionToTrigger);
        onClose();
      }
    }
    
    if (e.key === 'Escape') {
      onClose();
    }
  }, [onAction, onClose]);
  
  useEffect(() => {
    document.addEventListener('keydown', handleGlobalKeyDown);
    return () => document.removeEventListener('keydown', handleGlobalKeyDown);
  }, [handleGlobalKeyDown]);
  
  return (
    <div 
      className="context-menu"
      style={{ position: 'absolute', left: x, top: y }}
    >
      {menuItems.map(item => (
        <button 
          key={item.id}
          onClick={() => {
            onAction(item.action);
            onClose();
          }}
        >
          {item.label}
          <span className="shortcut">{item.shortcut}</span>
        </button>
      ))}
    </div>
  );
}

// File manager with cross-platform shortcuts
function FileManager({ files, onFileAction }) {
  const [selectedFiles, setSelectedFiles] = useState<string[]>([]);
  
  const handleKeyDown = (e: KeyboardEvent) => {
    const isCtrlCmd = isCtrlKeyPressed(e);
    
    if (isCtrlCmd) {
      switch (e.key.toLowerCase()) {
        case 'a':
          e.preventDefault();
          setSelectedFiles(files.map(f => f.id));
          break;
          
        case 'c':
          e.preventDefault();
          copyFilesToClipboard(selectedFiles);
          break;
          
        case 'x':
          e.preventDefault();
          cutFilesToClipboard(selectedFiles);
          break;
          
        case 'v':
          e.preventDefault();
          pasteFilesFromClipboard();
          break;
      }
    } else if (e.key === 'Delete') {
      e.preventDefault();
      onFileAction('delete', selectedFiles);
    }
  };
  
  const handleClick = (e: MouseEvent, fileId: string) => {
    const isCtrlCmd = isCtrlKeyPressed(e);
    
    if (isCtrlCmd) {
      // Multi-select with Ctrl/Cmd+click
      setSelectedFiles(prev => 
        prev.includes(fileId) 
          ? prev.filter(id => id !== fileId)
          : [...prev, fileId]
      );
    } else {
      // Single select
      setSelectedFiles([fileId]);
    }
  };
  
  return (
    <div className="file-manager" onKeyDown={handleKeyDown} tabIndex={0}>
      {files.map(file => (
        <div 
          key={file.id}
          className={`file ${selectedFiles.includes(file.id) ? 'selected' : ''}`}
          onClick={(e) => handleClick(e, file.id)}
        >
          {file.name}
        </div>
      ))}
    </div>
  );
}

Advanced Virtual Event Patterns

Complex scenarios combining virtual event detection with accessibility features:

import { isVirtualClick, isVirtualPointerEvent, isCtrlKeyPressed } from "@react-aria/utils";

// Accessible drag and drop with virtual event support
function DragDropItem({ item, onDragStart, onDrop }) {
  const [isDragging, setIsDragging] = useState(false);
  const [keyboardDragMode, setKeyboardDragMode] = useState(false);
  const elementRef = useRef<HTMLDivElement>(null);
  
  const handleMouseDown = (e: MouseEvent) => {
    if (!isVirtualClick(e)) {
      // Real mouse interaction
      setIsDragging(true);
      onDragStart(item);
    }
  };
  
  const handleKeyDown = (e: KeyboardEvent) => {
    const isCtrlCmd = isCtrlKeyPressed(e);
    
    if (e.key === ' ' && isCtrlCmd) {
      // Ctrl/Cmd+Space starts keyboard drag mode
      e.preventDefault();
      setKeyboardDragMode(true);
      announceToScreenReader('Drag mode activated. Use arrow keys to move, Space to drop.');
    } else if (keyboardDragMode) {
      switch (e.key) {
        case 'ArrowUp':
        case 'ArrowDown':
        case 'ArrowLeft':
        case 'ArrowRight':
          e.preventDefault();
          moveItemInDirection(e.key);
          break;
          
        case ' ':
          e.preventDefault();
          setKeyboardDragMode(false);
          onDrop(item);
          announceToScreenReader('Item dropped.');
          break;
          
        case 'Escape':
          e.preventDefault();
          setKeyboardDragMode(false);
          announceToScreenReader('Drag cancelled.');
          break;
      }
    }
  };
  
  const handleClick = (e: MouseEvent) => {
    if (isVirtualClick(e)) {
      // Keyboard activation
      const isCtrlCmd = isCtrlKeyPressed(e);
      
      if (isCtrlCmd) {
        // Ctrl/Cmd+Enter activates keyboard drag
        setKeyboardDragMode(true);
      } else {
        // Regular activation
        onItemActivate(item);
      }
    }
  };
  
  return (
    <div
      ref={elementRef}
      className={`drag-item ${isDragging ? 'dragging' : ''} ${keyboardDragMode ? 'keyboard-drag' : ''}`}
      draggable
      tabIndex={0}
      onMouseDown={handleMouseDown}
      onKeyDown={handleKeyDown}
      onClick={handleClick}
      role="button"
      aria-describedby="drag-instructions"
    >
      {item.name}
      <div id="drag-instructions" className="sr-only">
        Press Ctrl+Space to start keyboard drag mode
      </div>
    </div>
  );
}

// Touch-friendly button with virtual event awareness
function TouchFriendlyButton({ onPress, children, ...props }) {
  const [isPressed, setIsPressed] = useState(false);
  const [pressStartTime, setPressStartTime] = useState(0);
  
  const handlePointerDown = (e: PointerEvent) => {
    if (isVirtualPointerEvent(e)) {
      // AT-generated pointer event
      console.log('Assistive technology interaction');
      return;
    }
    
    setIsPressed(true);
    setPressStartTime(Date.now());
  };
  
  const handlePointerUp = (e: PointerEvent) => {
    if (isVirtualPointerEvent(e)) {
      return;
    }
    
    setIsPressed(false);
    
    const pressDuration = Date.now() - pressStartTime;
    
    // Different feedback for long vs short presses
    if (pressDuration > 500) {
      console.log('Long press detected');
      onLongPress?.(e);
    } else {
      onPress?.(e);
    }
  };
  
  const handleClick = (e: MouseEvent) => {
    if (isVirtualClick(e)) {
      // Virtual click from keyboard or AT
      onPress?.(e);
    }
    // Mouse clicks are handled by pointer events
  };
  
  return (
    <button
      className={`touch-button ${isPressed ? 'pressed' : ''}`}
      onPointerDown={handlePointerDown}
      onPointerUp={handlePointerUp}
      onClick={handleClick}
      {...props}
    >
      {children}
    </button>
  );
}

// Game controller with keyboard and virtual input support
function GameController({ onAction }) {
  const handleKeyDown = (e: KeyboardEvent) => {
    const isCtrlCmd = isCtrlKeyPressed(e);
    
    // Standard game controls
    switch (e.key) {
      case 'ArrowUp':
      case 'w':
      case 'W':
        onAction('move-up');
        break;
        
      case 'ArrowDown':
      case 's':
      case 'S':
        onAction('move-down');
        break;
        
      case 'ArrowLeft':
      case 'a':
      case 'A':
        onAction('move-left');
        break;
        
      case 'ArrowRight':
      case 'd':
      case 'D':
        onAction('move-right');
        break;
        
      case ' ':
        onAction('action');
        break;
        
      case 'Enter':
        onAction('confirm');
        break;
    }
    
    // Special combinations with Ctrl/Cmd
    if (isCtrlCmd) {
      switch (e.key.toLowerCase()) {
        case 'r':
          e.preventDefault();
          onAction('restart');
          break;
          
        case 'p':
          e.preventDefault();
          onAction('pause');
          break;
      }
    }
  };
  
  const handleClick = (e: MouseEvent, action: string) => {
    if (isVirtualClick(e)) {
      // Keyboard/AT activation of button
      announceToScreenReader(`${action} activated`);
    }
    
    onAction(action);
  };
  
  return (
    <div className="game-controller" onKeyDown={handleKeyDown} tabIndex={0}>
      <div className="dpad">
        <button onClick={(e) => handleClick(e, 'move-up')}>↑</button>
        <button onClick={(e) => handleClick(e, 'move-left')}>←</button>
        <button onClick={(e) => handleClick(e, 'move-right')}>→</button>
        <button onClick={(e) => handleClick(e, 'move-down')}>↓</button>
      </div>
      
      <div className="action-buttons">
        <button onClick={(e) => handleClick(e, 'action')}>Action</button>
        <button onClick={(e) => handleClick(e, 'confirm')}>Confirm</button>
      </div>
    </div>
  );
}

Browser Compatibility

Virtual event detection works consistently across all modern browsers:

  • Chrome/Edge: Full support for pointer events and virtual click detection
  • Firefox: Full support with proper AT integration
  • Safari: Full support including VoiceOver integration
  • Mobile browsers: Handles touch-to-click conversion properly

Accessibility Considerations

When working with virtual events:

  • Always support keyboard activation: Virtual clicks often come from Enter/Space key presses
  • Provide appropriate feedback: Screen readers expect different feedback for virtual vs. real clicks
  • Don't prevent default behavior unnecessarily: AT may depend on default behaviors
  • Test with real assistive technology: Virtual event detection helps but isn't a substitute for AT testing

Types

interface MouseEvent extends UIEvent {
  metaKey: boolean;
  ctrlKey: boolean;
  altKey: boolean;
  shiftKey: boolean;
  // ... other MouseEvent properties
}

interface PointerEvent extends MouseEvent {
  pointerId: number;
  pointerType: string;
  // ... other PointerEvent properties
}

interface KeyboardEvent extends UIEvent {
  key: string;
  metaKey: boolean;
  ctrlKey: boolean;
  altKey: boolean;
  shiftKey: boolean;
  // ... other KeyboardEvent properties
}

Install with Tessl CLI

npx tessl i tessl/npm-react-aria--utils

docs

animation-and-transitions.md

event-management.md

focus-and-accessibility.md

id-and-refs.md

index.md

links-and-navigation.md

miscellaneous-utilities.md

platform-detection.md

props-and-events.md

scrolling-and-layout.md

shadow-dom-support.md

state-and-effects.md

virtual-events-and-input.md

tile.json