
CSS Focus Styles for Keyboard Users Only

There is a fine line to be trodden between the aesthetic expectations of a project, and allowing suitable accessibility. I would always argue that accessibility far outweighs any importance visuals carries, but that can be an uphill struggle in teams where the distinction between technical UX and UI aesthetics has become blurred.
We are all familiar with the situation where interactive elements on‑page gain an unexpected blue glow:
- Text inputs when clicked into;
- Links and buttons when clicked upon.
It is these things that will have the hybrid UX/UI person frenetically walking up behind you to push their laptop in your face and frantically point at it.

This is inherited from the :focus pseudo‑class, jump onto Stack Overflow and you will find dozens of answers all saying the same thing: remove the outline, maybe even drop an !important in there for good measure.
*:focus { outline: none !important;}The key thing to bear in mind is that this bright outline is very important for keyboard or visually impaired users. I won't dwell on this point for too long because I hope to make it absolutely clear in a single, short sentence:
You must never just remove CSS outlines.
If you find yourself struggling against this one in a professional development environment, give them my email address. I'll explain it to them.
To placate both visuals and accessibility, there are three options you can explore:
- Style the outline. It doesn't actually have to be that default blue glow (which differs from browser to browser). As long as it is visually distinctive from the surrounding site, keyboard users will still be able to use it.
- Give the element more specific styling. You really should have a defined visual state for
:focusanyway, just make sure it is distinctive enough to show that the interaction is occurring. - Take the outlines away, but only for non‑keyboard users.
It is a combination of the second and third options that I most commonly implement. As I mentioned: you should already have a distinct style for your interactables anyway, remember the minimum you should be styling is default, :visited, :hover, :active, :focus. It often makes sense to combine these so that default and visited are the same, as are the other three:

So, with obvious enough visuals, you could argue that an outline isn't necessary at all.
Take Away Outlines for Keyboard Users Only
This is where things get a little more interesting. Although it should be a last resort, it is also possible to remove outlines for keyboard users, whilst retaining them for people traversing and interacting with the site via the keyboard.
:focus‑visible is intended to do just this by allowing you to apply different focus indicators based on the user's modality (i.e., mouse, touch, or keyboard). Here's an example from the MDN documentation which I've modified a little and moved into Sass using the parent selector:
.button { display: block; // Provide a fallback style for browsers // that don't support :focus-visible &:focus { outline: none; background: lightgrey; // Remove the focus indicator on mouse-focus for browsers // that do support :focus-visible &:not(:focus-visible) { background: transparent; } } // Draw a very noticeable focus style for keyboard-focus // on browsers that do support :focus-visible &:focus-visible { outline: 4px dashed darkorange; background: transparent; }}The problem here is that browser support remains very poor, and whilst there is a polyfill you could use, it is quite weighty for what is a relatively simple problem to solve.
For me, the answer comes from tying a keyboard and mouse event to the document and toggling a classname on the body:
const bodyEl = document.querySelector('body');const keyboardClass = 'keyboard-user';const handleKeydownOnce = (e) => { const { key } = e; if (key === 'Tab') { bodyEl.classList.add(keyboardClass); document.removeEventListener('keydown', handleKeydownOnce); document.addEventListener('mousedown', handleMousedownOnce); }};const handleMousedownOnce = (e) => { bodyEl.classList.remove(keyboardClass); document.removeEventListener('mousedown', handleMousedownOnce); document.addEventListener('keydown', handleKeydownOnce);};useEffect(() => { document.addEventListener('keydown', handleKeydownOnce); return () => { document.removeEventListener('keydown', handleKeydownOnce); };});What this does is:
- Use the React Effect Hook to add a
keydownevent listener, and remove it again on dismount. You could just as easily use vanilla JavaScript to achieve this if you aren't using React. - When a key event occurs, we check
event.keyto determine whether it was the Tab key that was pressed or not. In the past, we could have checked to see ifevent.keyCodeis9(the tab key), butkeyCodehas been depreciated for some time now, so it is fair to assume that support will begin to dwindle as time goes by. That said, support forKeyboardEvent.keyalso is not yet great so if you are supporting legacy Firefox or Internet Explorer, you may need to use a combination ofkeyand/orcodealongsidekeyCode. - If it was a tab key, we know this is somebody attempting to traverse the page using the keyboard. Set the classname '
keyboard‑user' onbody, remove the keyboard event listener, and instead attach an event to listen for mouse events. - If there is a mouse event, we know this user is now using the mouse instead. We remove the classname from the
body, and reinstate the keyboard listener.
In this way, we minimise event listeners (which can lead to performance lag) and can determine between different interaction modalities for our users, even if they switch between keyboard and mouse.
The final piece of the puzzle is a simple piece of CSS (or Sass):
body { &:not(.keyboard-user) { *:focus, *:active { outline: none; } }}This effectively removes the outline for non‑keyboard users and allows the browser default interaction highlight (assuming it has not been overridden elsewhere in your project) when using the keyboard. This is exactly how it is implemented here on my site.
As a final note on the subject: it should be considered that outlines aren't only indicative of, or for, keyboard users: the highlight allows for much easier passage through your site. Unless you are able to implement really clear visual indicators for your interactables, I would always advise great caution in overriding browser defaults.
Related Articles

Parent Selectors in CSS and Sass. 
HTML Video and the preload Attribute. HTML Video and the
preloadAttribute
Optimising HTML Markup for SEO. Optimising HTML Markup for SEO

Understanding Phantom window.resize Events in iOS. Understanding Phantom
window.resizeEvents in iOS
Enhancing Web Typography with text‑wrap: balance. Enhancing Web Typography with
text‑wrap: balance
Using Viewport Units in CSS: vw and vh. Using Viewport Units in CSS:
vwandvh
Understanding Media Queries in CSS. Understanding Media Queries in CSS

React vs. Vue vs. Angular. React vs. Vue vs. Angular
JavaScript’s Math.random(). JavaScript's
Math.random()
The arguments Object vs. Rest Parameters in JavaScript. The
argumentsObject vs. Rest Parameters in JavaScript
The Palindrome Number Problem: Strings vs. Maths in JavaScript. The Palindrome Number Problem: Strings vs. Maths in JavaScript
Use CSS to Change the Mouse Cursor. Use CSS to Change the Mouse Cursor