Clean Markup

Separating Mouse and Keyboard Focus Rings with focus-visible

Separating Mouse and Keyboard Focus Rings with focus-visible

One of the most common—and damaging—accessibility regressions on the web is button:focus { outline: none; }, usually added because designers or stakeholders dislike seeing a focus ring linger on a button after clicking it with a mouse. Removing :focus outlines outright leaves keyboard and switch-device users completely blind to where their cursor is positioned on the page.

The :focus-visible pseudo-class solves this dilemma natively. The browser uses heuristics to determine the input modality: when a user navigates via keyboard (Tab, Shift + Tab, or arrow keys) or interacts with a text input, :focus-visible matches and displays a prominent focus indicator. When a user clicks a button with a mouse or taps on a touchscreen, :focus matches but :focus-visible does not.

Example

/* 1. Fallback for older browsers: always show a clear focus ring */
.btn:focus {
  outline: 3px solid #04ae92;
  outline-offset: 2px;
}

/* 2. Remove the ring ONLY when focused via mouse/touch in supporting browsers */
.btn:focus:not(:focus-visible) {
  outline: none;
}

/* 3. Explicit high-contrast ring for keyboard navigation */
.btn:focus-visible {
  outline: 3px solid #04ae92;
  outline-offset: 2px;
}

Input : Custom interactive buttons, navigation links, and disclosure toggles
Output : Crisp, unmistakable focus rings during Tab keyboard navigation and clean visuals after mouse clicks.

Implementation

<nav class="action-bar" aria-label="Article actions">
  <button type="button" class="action-btn">Bookmark Article</button>
  <button type="button" class="action-btn">Copy Link</button>
  <a href="#comments" class="action-link">Jump to Comments</a>
</nav>
.action-btn,
.action-link {
  display: inline-flex;
  align-items: center;
  padding: 10px 18px;
  border: 1px solid #0f172a;
  border-radius: 6px;
  background-color: #ffffff;
  color: #0f172a;
  font-size: 15px;
  font-weight: 600;
  text-decoration: none;
  cursor: pointer;
  transition: background-color 150ms ease, color 150ms ease;
}

.action-btn:hover,
.action-link:hover {
  background-color: #0f172a;
  color: #ffffff;
}

/* Progressive enhancement pattern for universal keyboard accessibility */
.action-btn:focus,
.action-link:focus {
  outline: 3px solid #04ae92;
  outline-offset: 3px;
}

.action-btn:focus:not(:focus-visible),
.action-link:focus:not(:focus-visible) {
  outline: none;
}

.action-btn:focus-visible,
.action-link:focus-visible {
  outline: 3px solid #04ae92;
  outline-offset: 3px;
  box-shadow: 0 0 0 6px rgba(4, 174, 146, 0.2);
}

Main Point: Never write outline: none on :focus without pairing it with :focus:not(:focus-visible) and a high-contrast :focus-visible rule so keyboard users always know where focus is located.

Credits

Photo by ales nesetril

← Home