Focus indicators are essential visual cues that show users which element currently has keyboard focus. They’re crucial for keyboard navigation and help users understand where they are on a page. Good focus indicators ensure that all users can navigate your website effectively.

Why Focus Indicators Matter

When users navigate with a keyboard, they need to know which element currently has focus. Without clear focus indicators, users can become lost and frustrated. Good focus indicators ensure that:

  • Keyboard users can see which element is active
  • Users understand the current navigation state
  • Focus is clearly visible in all lighting conditions
  • All users benefit from clear visual feedback

Basic Focus Indicator Implementation

The foundation of accessible focus indicators starts with proper CSS:

/* Basic focus indicator */
*:focus {
  outline: 2px solid #007bff;
  outline-offset: 2px;
}

/* Alternative using box-shadow */
*:focus {
  box-shadow: 0 0 0 2px #007bff;
  outline: none;
}

Key Points:

  • Always provide a visible focus indicator
  • Use high contrast colors
  • Make indicators at least 2px thick
  • Use outline-offset for better visibility

Focus Indicators for Different Elements

Buttons

.btn {
  padding: 0.5rem 1rem;
  border: 1px solid #ccc;
  border-radius: 4px;
  background: #fff;
  cursor: pointer;
}

.btn:focus {
  outline: 2px solid #007bff;
  outline-offset: 2px;
  border-color: #007bff;
}

.btn:focus:not(:focus-visible) {
  outline: none;
  border-color: #ccc;
}
.link {
  color: #007bff;
  text-decoration: underline;
  text-underline-offset: 0.2em;
}

.link:focus {
  outline: 2px solid #007bff;
  outline-offset: 2px;
  text-decoration-thickness: 2px;
}

.link:focus:not(:focus-visible) {
  outline: none;
  text-decoration-thickness: 1px;
}

Form Inputs

.input {
  padding: 0.5rem;
  border: 2px solid #ccc;
  border-radius: 4px;
  font-size: 1rem;
}

.input:focus {
  outline: 2px solid #007bff;
  outline-offset: 2px;
  border-color: #007bff;
}

.input:focus:not(:focus-visible) {
  outline: none;
  border-color: #ccc;
}

Advanced Focus Indicator Patterns

Custom Focus Indicators

/* Custom focus indicator with multiple styles */
.custom-focus:focus {
  outline: 2px solid #007bff;
  outline-offset: 2px;
  box-shadow: 0 0 0 4px rgba(0, 123, 255, 0.2);
  border-color: #007bff;
  transform: scale(1.02);
  transition: all 0.2s ease;
}

/* High contrast focus indicator */
.high-contrast-focus:focus {
  outline: 3px solid #000000;
  outline-offset: 2px;
  box-shadow: 0 0 0 6px #ffffff;
}

Focus Indicators for Different States

/* Default focus */
.btn:focus {
  outline: 2px solid #007bff;
  outline-offset: 2px;
}

/* Hover state */
.btn:hover {
  background-color: #f8f9fa;
}

/* Active state */
.btn:active {
  transform: translateY(1px);
}

/* Focus and hover together */
.btn:focus:hover {
  outline: 2px solid #0056b3;
  background-color: #e9ecef;
}

Focus Indicators for Custom Components

/* Custom checkbox focus */
.custom-checkbox:focus {
  outline: 2px solid #007bff;
  outline-offset: 2px;
  border-radius: 2px;
}

/* Custom radio button focus */
.custom-radio:focus {
  outline: 2px solid #007bff;
  outline-offset: 2px;
  border-radius: 50%;
}

/* Custom select focus */
.custom-select:focus {
  outline: 2px solid #007bff;
  outline-offset: 2px;
  border-color: #007bff;
}

CSS Variables for Consistent Focus

:root {
  --focus-color: #007bff;
  --focus-color-hover: #0056b3;
  --focus-outline-width: 2px;
  --focus-outline-offset: 2px;
  --focus-box-shadow: 0 0 0 4px rgba(0, 123, 255, 0.2);
}

[data-theme="dark"] {
  --focus-color: #4dabf7;
  --focus-color-hover: #74c0fc;
  --focus-box-shadow: 0 0 0 4px rgba(77, 171, 247, 0.3);
}

/* Global focus styles */
*:focus {
  outline: var(--focus-outline-width) solid var(--focus-color);
  outline-offset: var(--focus-outline-offset);
}

*:focus:not(:focus-visible) {
  outline: none;
}

Focus Management with JavaScript

Programmatic Focus

// Focus management for modals
function openModal() {
  const modal = document.getElementById('modal');
  const closeButton = modal.querySelector('.close-button');
  
  modal.style.display = 'block';
  closeButton.focus(); // Focus the close button
  
  // Trap focus within modal
  const focusableElements = modal.querySelectorAll(
    'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
  );
  
  const firstElement = focusableElements[0];
  const lastElement = focusableElements[focusableElements.length - 1];
  
  // Handle tab key
  modal.addEventListener('keydown', function(e) {
    if (e.key === 'Tab') {
      if (e.shiftKey) {
        if (document.activeElement === firstElement) {
          e.preventDefault();
          lastElement.focus();
        }
      } else {
        if (document.activeElement === lastElement) {
          e.preventDefault();
          firstElement.focus();
        }
      }
    }
  });
}

// Restore focus when modal closes
function closeModal() {
  const modal = document.getElementById('modal');
  modal.style.display = 'none';
  
  // Restore focus to the element that opened the modal
  if (lastFocusedElement) {
    lastFocusedElement.focus();
  }
}

Focus Tracking

// Track the last focused element
let lastFocusedElement = null;

document.addEventListener('focusin', function(e) {
  lastFocusedElement = e.target;
});

// Focus indicator for dynamic content
function addFocusIndicator(element) {
  element.addEventListener('focus', function() {
    this.classList.add('has-focus');
  });
  
  element.addEventListener('blur', function() {
    this.classList.remove('has-focus');
  });
}

Common Focus Indicator Mistakes

❌ Bad: Removing focus indicators

/* Never do this! */
*:focus {
  outline: none;
}

Problems:

  • Keyboard users can’t see which element has focus
  • Violates WCAG 2.4.7 (Focus Visible)

❌ Bad: Low contrast focus indicators

.focus-bad:focus {
  outline: 1px solid #ccc;
  /* Contrast ratio: 1.6:1 - Very poor */
}

Problems:

  • Focus indicator is barely visible
  • Users can’t see focus in bright lighting

❌ Bad: Focus indicators that disappear

.focus-disappears:focus {
  outline: 2px solid #007bff;
}

.focus-disappears:focus:hover {
  outline: none; /* Focus disappears on hover */
}

Problems:

  • Focus indicator disappears when user hovers
  • Confusing for keyboard users

❌ Bad: Inconsistent focus indicators

.button:focus {
  outline: 2px solid #007bff;
}

.link:focus {
  background-color: yellow; /* Different style */
}

.input:focus {
  border-color: red; /* Another different style */
}

Problems:

  • Users don’t know what focus looks like
  • Inconsistent user experience

Testing Focus Indicators

Manual Testing

  1. Keyboard Navigation: Tab through all interactive elements
  2. Focus Visibility: Ensure focus is clearly visible
  3. Focus Order: Verify logical tab order
  4. Focus Persistence: Check that focus doesn’t disappear unexpectedly

Automated Testing

// Test focus indicators programmatically
function testFocusIndicators() {
  const focusableElements = document.querySelectorAll(
    'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
  );
  
  focusableElements.forEach(element => {
    element.focus();
    
    // Check if element has visible focus indicator
    const computedStyle = window.getComputedStyle(element);
    const hasOutline = computedStyle.outline !== 'none';
    const hasBoxShadow = computedStyle.boxShadow !== 'none';
    const hasBorder = computedStyle.borderColor !== 'initial';
    
    if (!hasOutline && !hasBoxShadow && !hasBorder) {
      console.warn('Element lacks visible focus indicator:', element);
    }
    
    element.blur();
  });
}

Focus Indicator Guidelines

Use High Contrast Colors

/* Good: High contrast focus indicator */
.focus-good:focus {
  outline: 2px solid #007bff;
  outline-offset: 2px;
  /* Contrast ratio: 7:1 - AAA compliant */
}

/* Bad: Low contrast focus indicator */
.focus-bad:focus {
  outline: 1px solid #ccc;
  /* Contrast ratio: 1.6:1 - Very poor */
}

Make Indicators Thick Enough

/* Good: Thick enough to be visible */
.focus-good:focus {
  outline: 2px solid #007bff;
  outline-offset: 2px;
}

/* Bad: Too thin to be visible */
.focus-bad:focus {
  outline: 1px solid #007bff;
  outline-offset: 1px;
}

Use Consistent Styles

/* Good: Consistent focus indicators */
*:focus {
  outline: 2px solid #007bff;
  outline-offset: 2px;
}

/* Bad: Inconsistent focus indicators */
.button:focus { outline: 2px solid #007bff; }
.link:focus { background: yellow; }
.input:focus { border-color: red; }

Advanced Focus Patterns

Focus Indicators for High Contrast Mode

@media (prefers-contrast: high) {
  *:focus {
    outline: 3px solid;
    outline-offset: 2px;
  }
  
  button:focus,
  input:focus,
  select:focus,
  textarea:focus {
    outline: 3px solid;
    outline-offset: 2px;
  }
}

Focus Indicators for Reduced Motion

@media (prefers-reduced-motion: reduce) {
  *:focus {
    transition: none;
    animation: none;
  }
}

Custom Focus Indicators for Complex Components

/* Custom focus for card components */
.card:focus-within {
  outline: 2px solid #007bff;
  outline-offset: 2px;
  box-shadow: 0 4px 8px rgba(0, 123, 255, 0.2);
}

/* Custom focus for navigation menus */
.nav-menu:focus-within {
  outline: 2px solid #007bff;
  outline-offset: 2px;
  background-color: rgba(0, 123, 255, 0.1);
}

Remember: Focus indicators are essential for keyboard accessibility. Always provide clear, visible focus indicators and test them thoroughly with keyboard navigation.