or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

index.md
tile.json

index.mddocs/

Focus Visible

Focus Visible is a polyfill for the CSS :focus-visible pseudo-selector that adds a focus-visible class to focused elements when keyboard navigation is used. It enables developers to create accessible focus indicators that appear only when users navigate with the keyboard, while hiding focus rings for mouse interactions.

Package Information

  • Package Name: focus-visible
  • Package Type: npm
  • Language: JavaScript
  • Installation: npm install focus-visible

Core Imports

Since this is a polyfill, it can be used in multiple ways:

Script Tag (Recommended)

<script src="/node_modules/focus-visible/dist/focus-visible.min.js"></script>

ES Modules

import "focus-visible";

CommonJS

require("focus-visible");

Basic Usage

1. Include the Polyfill

Add the script to your HTML page. The polyfill automatically applies to the document when loaded:

<!DOCTYPE html>
<html>
  <head>
    <link rel="stylesheet" href="styles.css">
  </head>
  <body>
    <!-- Your content -->
    <button>Click me</button>
    <input type="text" placeholder="Type here">
    
    <!-- Include polyfill before closing body tag -->
    <script src="/node_modules/focus-visible/dist/focus-visible.min.js"></script>
  </body>
</html>

2. Update Your CSS

Use the .js-focus-visible class to conditionally hide default focus styles for mouse users:

/* Hide focus indicator for mouse users */
.js-focus-visible :focus:not(.focus-visible) {
  outline: none;
}

/* Optional: Enhanced focus styles for keyboard users */
.js-focus-visible .focus-visible {
  outline: 2px solid #4A90E2;
  outline-offset: 2px;
}

Alternative: Using Data Attributes

For frameworks that overwrite classes:

[data-js-focus-visible] :focus:not([data-focus-visible-added]) {
  outline: none;
}

Architecture

The polyfill works by:

  1. Detecting Interaction Mode: Uses heuristics to determine if the user is navigating with keyboard or mouse/pointer
  2. Adding Classes: Adds focus-visible class to elements when keyboard focus is detected
  3. Global Coordination: Manages focus state at the document level while supporting Shadow DOM
  4. Event Broadcasting: Provides coordination events for lazy-loading scenarios

Key behavioral rules:

  • Text inputs (input[type=text], textarea, etc.) always show focus indicators regardless of interaction method
  • Tab navigation and arrow keys trigger keyboard mode
  • Mouse/pointer interactions switch to mouse mode
  • The polyfill remembers the interaction mode across focus changes

Capabilities

Automatic Polyfill Application

The polyfill automatically applies to the main document when the script loads.

/**
 * Automatically applies focus-visible polyfill to the document when script loads
 * This happens immediately when the script is included
 */
// No function call needed - automatic initialization
// Equivalent to: applyFocusVisiblePolyfill(document)

Manual Shadow DOM Application

Apply the polyfill to Shadow DOM components manually.

/**
 * Applies the focus-visible polyfill to a specific scope
 * @param scope - Document or ShadowRoot to apply polyfill to
 */
function applyFocusVisiblePolyfill(scope: Document | ShadowRoot): void;

Usage Example:

// Check if polyfill is available
if (window.applyFocusVisiblePolyfill != null) {
  // Apply to a shadow root
  window.applyFocusVisiblePolyfill(myComponent.shadowRoot);
}

Lazy Loading Coordination

Listen for the polyfill ready event when lazy-loading the script.

/**
 * Custom event fired when polyfill is loaded and ready
 * Event name: 'focus-visible-polyfill-ready'
 * Bubbles: false
 * Cancelable: false
 * Detail: {}
 */
interface FocusVisiblePolyfillReadyEvent extends CustomEvent {
  type: 'focus-visible-polyfill-ready';
}

Usage Example:

// Listen for polyfill ready event
window.addEventListener('focus-visible-polyfill-ready', () => {
  window.applyFocusVisiblePolyfill(myComponent.shadowRoot);
}, { once: true });

// Lazy load the polyfill script
const script = document.createElement('script');
script.src = '/path/to/focus-visible.min.js';
document.head.appendChild(script);

CSS Classes and Attributes

Classes Added by Polyfill

/* Added to document.documentElement when polyfill is active */
.js-focus-visible { }

/* Added to focused elements when keyboard focus is detected */
.focus-visible { }

Important: If developers manually add the focus-visible class to an element, it will not be removed on blur. Only classes added by the polyfill (marked with data-focus-visible-added) are automatically managed.

Data Attributes Added by Polyfill

<!-- Added to document.documentElement or shadow host when polyfill is active -->
<html data-js-focus-visible>

<!-- Added to focused elements when polyfill adds .focus-visible class -->
<button data-focus-visible-added>Button</button>

CSS Integration Patterns

Basic Pattern (Recommended)

/* Hide default focus for mouse users only when polyfill is active */
.js-focus-visible :focus:not(.focus-visible) {
  outline: none;
}

/* Optional: Enhanced focus styles for keyboard users */
.js-focus-visible .focus-visible {
  outline: 2px solid #0066cc;
  outline-offset: 2px;
}

Framework-Safe Pattern

For frameworks that overwrite CSS classes:

[data-js-focus-visible] :focus:not([data-focus-visible-added]) {
  outline: none;
}

[data-focus-visible-added] {
  outline: 2px solid #0066cc;
  outline-offset: 2px;
}

Backwards Compatibility Pattern

For environments where the polyfill might not be loaded:

/* Provide default focus styles */
button:focus {
  outline: 2px solid #0066cc;
}

/* Hide focus for mouse users when polyfill is active */
.js-focus-visible button:focus:not(.focus-visible) {
  outline: none;
}

Input Types That Always Show Focus

The following input types always receive the focus-visible class regardless of interaction method:

  • input[type="text"]
  • input[type="search"]
  • input[type="url"]
  • input[type="tel"]
  • input[type="email"]
  • input[type="password"]
  • input[type="number"]
  • input[type="date"]
  • input[type="month"]
  • input[type="week"]
  • input[type="time"]
  • input[type="datetime"]
  • input[type="datetime-local"]
  • textarea elements
  • Elements with contenteditable attribute

Types

/**
 * Global function available when polyfill is loaded
 */
declare global {
  interface Window {
    applyFocusVisiblePolyfill: (scope: Document | ShadowRoot) => void;
  }
}

/**
 * Supported scopes for polyfill application
 */
type PolyfillScope = Document | ShadowRoot;

/**
 * Input types that always trigger focus-visible class regardless of interaction method
 */
interface InputTypesAllowlist {
  text: true;
  search: true;
  url: true;
  tel: true;
  email: true;
  password: true;
  number: true;
  date: true;
  month: true;
  week: true;
  time: true;
  datetime: true;
  'datetime-local': true;
}

/**
 * CSS classes added by the polyfill
 */
interface FocusVisibleClasses {
  /** Added to document element when polyfill is active */
  'js-focus-visible': string;
  /** Added to focused elements during keyboard navigation */
  'focus-visible': string;
}

/**
 * Data attributes added by the polyfill
 */
interface FocusVisibleAttributes {
  /** Added to document/host element when polyfill is active */
  'data-js-focus-visible': string;
  /** Added to elements when polyfill adds focus-visible class */
  'data-focus-visible-added': string;
}