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
Focus indicators are the visual equivalent of a cursor for keyboard users - they show exactly where the user is on the page and what element they can interact with. Without clear focus indicators, keyboard navigation becomes impossible and users can become completely lost.
For keyboard-only users: Focus indicators are their primary way of knowing where they are on the page. Users with motor impairments, tremors, or limited dexterity often rely entirely on keyboard navigation. Without visible focus indicators, they have no way to know which element is active or where they can interact.
For users with visual impairments: Focus indicators must be clearly visible and have sufficient contrast. Users with low vision need to be able to see focus indicators easily to understand their current position on the page.
For users with cognitive disabilities: Clear focus indicators help users understand the current state of the page and what actions are available. Consistent focus indicators reduce cognitive load and make navigation more predictable.
For users with motor impairments: Focus indicators help users understand exactly where they need to interact. Without clear indicators, users might click or activate the wrong elements, leading to frustration and errors.
For all users: Focus indicators provide essential visual feedback that improves the overall user experience. They help users understand the current state of the page and what actions are available.
The Impact of Missing Focus Indicators
When focus indicators are missing or inadequate, it creates significant barriers:
- Keyboard users cannot navigate the website at all - they have no way to know where they are
- Users with motor impairments may click on wrong elements or become frustrated
- All users lose important visual feedback about the current page state
- Screen reader users may not understand the current focus position
This is why WCAG 2.4.7 (Focus Visible) specifically requires that focus indicators be visible and meet contrast requirements.
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 - keyboard users rely on this to know where they are on the page
- Use high contrast colors - ensures the focus indicator is visible in all lighting conditions and for users with visual impairments
- Make indicators at least 2px thick - provides sufficient visibility and meets WCAG requirements for focus indicator thickness
- Use outline-offset for better visibility - creates space between the element and the focus indicator, making it easier to see
- Never remove focus indicators with CSS - even if you think they look better without them, they’re essential for accessibility
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;
}
Links
.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
- Keyboard Navigation: Tab through all interactive elements
- Focus Visibility: Ensure focus is clearly visible
- Focus Order: Verify logical tab order
- 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.