Enable :focus only on keyboard use (or tab press)

Update: This issue may no longer be relevant

Some other posters have mentioned the :focus-visible pseudo class - which now has decent browser support...

I would like to add, that based on the spec which covers the :focus-visible pseudo class, browsers should now only indicate focus when it is helpful to the user - such as in cases where the user interacts with the page via a keyboard or some other non-pointing device

This basically means that the original issue is no longer relevant, because now, when a user clicks/taps a button (or another focusable element), the User Agent won't show the focus ring anymore - even though the button is focused - because in this case the focus ring isn't helpful to the user.

From the spec:

While the :focus pseudo-class always matches the currently-focused element, UAs only sometimes visibly indicate focus (such as by drawing a “focus ring”), instead using a variety of heuristics to visibly indicate the focus only when it would be most helpful to the user. The :focus-visible pseudo-class matches a focused element in these situations only...

Indeed, as of version 90, Chromium’s User Agent stylesheet switched from :focus to :focus-visible, and as a result of this change, button clicks and taps no longer invoke focus rings

Also, as of version 87, Firefox also uses :focus-visible on their User Agent style.

All that being said, if custom focus styles are needed, since focus styles have now shifted from :focus to :focus-visible, when overriding the default styles with custom focus styles - the :focus-visible pseudo class should be used.

Something like this:

button:focus-visible {
  /* remove default focus style */
  outline: none;
  /* custom focus styles */
  box-shadow: 0 0 2px 2px #51a7e8;
  color: lime;
}

Backwards Compatibility:

The possible problem with using :focus-visible like this, is that browsers which don't support :focus-visible, will show the default focus ring, which may not be clear or visible - depending on the design.

Šime Vidas, in this article, describes a viable strategy to currently use the :focus-visible pseudo class - which would work even in browsers which don't yet support :focus-visible -

A good way to start using :focus-visible today is to define the focus styles in a :focus rule and then immediately undo these same styles in a :focus:not(:focus-visible) rule. This is admittedly not the most elegant and intuitive pattern, but it works well in all browsers:

Browsers that don’t support :focus-visible use the focus styles defined in the :focus rule and ignore the second style rule completely (because :focus-visible is unknown to them).

In browsers that do support :focus-visible, the second style rule reverts the focus styles defined in the :focus rule if the :focus-visible state isn’t active as well. In other words, the focus styles defined in the :focus rule are only in effect when :focus-visible is also active.

button:focus {
  outline: none;
  background: #ffdd00; /* gold */
}

button:focus:not(:focus-visible) {
  background: white; /* undo gold */
}

Original Answer:

This excellent article by Roman Komarov poses a viable solution for achieving keyboard-only focus styles for buttons, links and other container elements such as spans or divs (which are artificially made focusable with the tabindex attribute)

The Solution:

button {
  -moz-appearance: none;
  -webkit-appearance: none;
  background: none;
  border: none;
  outline: none;
  font-size: inherit;
}

.btn {
  all: initial;
  margin: 1em;
  display: inline-block; 
}

.btn__content {
  background: orange;
  padding: 1em;
  cursor: pointer;
  display: inline-block;
}


/* Fixing the Safari bug for `<button>`s overflow */
.btn__content {
    position: relative;
}

/* All the states on the inner element */
.btn:hover > .btn__content  {
    background: salmon;
}

.btn:active > .btn__content  {
    background: darkorange;
}

.btn:focus > .btn__content  {
    box-shadow: 0 0 2px 2px #51a7e8;
    color: lime;
}

/* Removing default outline only after we've added our custom one */
.btn:focus,
.btn__content:focus {
    outline: none;
}
<h2>Keyboard-only focus styles</h2>

<button id="btn" class="btn" type="button">
    <span class="btn__content" tabindex="-1">
        I'm a button!
    </span>
</button>

<a class="btn" href="#x">
    <span class="btn__content" tabindex="-1">
        I'm a link!
    </span>
</a>

<span class="btn" tabindex="0">
    <span class="btn__content" tabindex="-1">
        I'm a span!
    </span>
</span>

<p>Try clicking any of the the 3 focusable elements above - no focus styles will show</p>
<p>Now try tabbing - behold - focus styles</p>

Codepen

  1. Wrap the content of the original interactive element inside an additional inner element with tabindex="-1" (see explanation below)

So instead of say:

<button id="btn" class="btn" type="button">I'm a button!</button>

do this:

<button id="btn" class="btn" type="button">
    <span class="btn__content" tabindex="-1">
        I'm a button!
    </span>
</button>
  1. Move the css styling to the inner element (layout css should remain on the original outer element) - so the width / height of the outer element come from the inner one etc.

  2. Remove default focus styling from both outer and inner elements:

    .btn:focus, .btn__content:focus { outline: none; }

  3. Add focus styling back to the inner element only when the outer element has focus:

    .btn:focus > .btn__content { box-shadow: 0 0 2px 2px #51a7e8; /* keyboard-only focus styles / color: lime; / keyboard-only focus styles */ }

Why does this work?

The trick here is setting the inner element with tabindex="-1" - see MDN:

A negative value (usually tabindex="-1" means that the element should be focusable, but should not be reachable via sequential keyboard navigation...

So the element is focusable via mouse clicks or programatically, but on the other hand - it can't be reached via keyboard 'tabs'.

So when the interactive element is clicked - the inner element gets the focus. No focus styles will show because we have removed them.

.btn:focus,
.btn__content:focus {
    outline: none;
}

Note that only 1 DOM element can be focused at a given time (and document.activeElement returns this element) - so only the inner element will be focused.

On the other hand: when we tab using the keyboard - only the outer element will get the focus (remember: the inner element has tabindex="-1" and isn't reachable via sequential keyboard navigation) [Note that for inherently non-focusable outer elements like a clickable <div> - we have to artificially make them focusable by adding tabindex="0"]

Now our CSS kicks in and adds the keyboard-only focus styles to the inner element.

.btn:focus > .btn__content  {
    box-shadow: 0 0 2px 2px #51a7e8; /* keyboard-only focus styles */
    color: lime; /* keyboard-only focus styles */
} 

Of course, we want to make sure that when we tab and press enter - we haven't broken our interactive element and the javascript will run.

Here is a demo to show that this is indeed the case, note though that you only get this for free (ie pressing enter to cause a click event) for inherently interactive elements like buttons and links... for other elements such as spans - you need to code that up manually :)

//var elem = Array.prototype.slice.call(document.querySelectorAll('.btn'));
var btns = document.querySelectorAll('.btn');
var fakeBtns = document.querySelectorAll('.btn[tabindex="0"]');


var animate = function() {
  console.log('clicked!');
}

var kbAnimate = function(e) {
  console.log('clicking fake btn with keyboard tab + enter...');
  var code = e.which;
  // 13 = Return, 32 = Space
  if (code === 13) {
    this.click();
  }  
}

Array.from(btns).forEach(function(element) {
  element.addEventListener('click', animate);
});

Array.from(fakeBtns).forEach(function(element) {
  element.addEventListener('keydown', kbAnimate);
});
button {
  -moz-appearance: none;
  -webkit-appearance: none;
  background: none;
  border: none;
  outline: none;
  font-size: inherit;
}

.btn {
  all: initial;
  margin: 1em;
  display: inline-block; 
}

.btn__content {
  background: orange;
  padding: 1em;
  cursor: pointer;
  display: inline-block;
}


/* Fixing the Safari bug for `<button>`s overflow */
.btn__content {
    position: relative;
}

/* All the states on the inner element */
.btn:hover > .btn__content  {
    background: salmon;
}

.btn:active > .btn__content  {
    background: darkorange;
}

.btn:focus > .btn__content  {
    box-shadow: 0 0 2px 2px #51a7e8;
    color: lime;
}

/* Removing default outline only after we've added our custom one */
.btn:focus,
.btn__content:focus {
    outline: none;
}
<h2>Keyboard-only focus styles</h2>

<button id="btn" class="btn" type="button">
    <span class="btn__content" tabindex="-1">
        I'm a button!
    </span>
</button>

<a class="btn" href="#x">
    <span class="btn__content" tabindex="-1">
        I'm a link!
    </span>
</a>

<span class="btn" tabindex="0">
    <span class="btn__content" tabindex="-1">
        I'm a span!
    </span>
</span>

<p>Try clicking any of the the 3 focusable elements above - no focus styles will show</p>
<p>Now try tabbing + enter - behold - our interactive elements work</p>

Codepen


NB:

  1. Although this seems like an overly-complicated solution, for a non-javascript solution it's actually quite impressive. Simpler css-only 'solutions' involving :hover and :active pseudo class styling simply don't work. (unless of course you assume that the interactive element disappears immediately on click like a button within a modal say)

button {
  -moz-appearance: none;
  -webkit-appearance: none;
  background: none;
  border: none;
  font-size: inherit;
}

.btn {
  margin: 1em;
  display: inline-block; 
  background: orange;
  padding: 1em;
  cursor: pointer;
}

.btn:hover, .btn:active {
  outline: none;
}
<h2>Remove css :focus outline only on :hover and :active states</h2>

<button class="btn" type="button">I'm a button!</button>

<a class="btn" href="#x">I'm a link!</a>

<span class="btn" tabindex="0">I'm a span!</span>

<h3>Problem: Click on an interactive element.As soon as you hover out - you get the focus styling back - because it is still focused (at least regarding the button and focusable span) </h3>

Codepen

  1. This solution isn't perfect: firefox on windows will still get focus styles for buttons on click - but that seems to be a firefox bug (see the article)

  2. When browsers implement the :fo­cus-ring pseudo class - there may be a much simpler solution to this problem - (see the article) For what it's worth, there is a polyfill for :focus-ring - see this article by Chris DeMars


A pragmatic alternative to keyboard-only focus styles

So achieving keyboard-only focus styles is surprisingly difficult. One alternative / workaround which is much simpler and may both fulfil the designer's expectations and also be accessible - would be to style focus just like you would style for hover.

Codepen

So although technically this is not implementing keyboard-only styles, it essentially removes the need for keyboard-only styles.


Removing outline is terrible for accessibility! Ideally, the focus ring shows up only when the user intends to use the keyboard.

2018 Answer: Use :focus-visible. It's currently a W3C proposal for styling keyboard-only focus using CSS. Until major browsers support it, you can use this robust polyfill. It doesn't require adding extra elements or altering the tabindex.

/* Remove outline for non-keyboard :focus */
*:focus:not(.focus-visible) {
  outline: none;
}

/* Optional: Customize .focus-visible */
.focus-visible {
  outline-color: lightgreen;
}

I also wrote a more detailed post just in case you need more info.


Case study: Facebook login page

Facebook is using a tiny bit of Javascript on their login page right now (June 2018).

The Javascript detects when the user has clicked their mouse or used their keyboard, and toggles a class on and off on the body: <body class="using-mouse">

Then CSS rules can use that class to show or hide the appropriate focus styling on the relevant elements.

Here is some example code (also available on CodePen). Compare clicking and tabbing.

// Let the document know when the mouse is being used
document.body.addEventListener('mousedown', function() {
  document.body.classList.add('using-mouse');
});

// Re-enable focus styling when Tab is pressed
document.body.addEventListener('keydown', function(event) {
  if (event.keyCode === 9) {
    document.body.classList.remove('using-mouse');
  }
});

// Alternatively, re-enable focus styling when any key is pressed
//document.body.addEventListener('keydown', function() {
//  document.body.classList.remove('using-mouse');
//});
/* The default outline styling, for greatest accessibility. */
/* You can skip this to just use the browser's defaults. */
:focus {
  outline: #08f auto 2px;
}

/* When mouse is detected, ALL focused elements have outline removed. */
body.using-mouse :focus {
  outline: none;
}
<input>
<button>Submit</button>

Note that :focus above is equivalent to *:focus, matching all elements. If you only wanted to remove styling from buttons, you could put button:focus there instead.


Case study: GMail login page

Alternatively, at that time GMail was just styling focused buttons with a heavier shadow than unfocused buttons, regardless of whether the user was on mouse or keyboard.

This is simple to implement and understand, and doesn't require any Javascript.

:focus {
  outline: none;
  box-shadow: 0 0px 16px #0005;
}

But it's a compromise. It conveys focus information that mouse users aren't really interested in, and it might be a bit too subtle for keyboard users.

Still, this compromise is probably better than either one of the extremes (a strong outline for all users, or no outline at all).


StackOverflow's primary buttons use a similar approach to GMail, but with a more stylised look:

box-shadow: inset 0 1px 0 0 rgba(102,191,255,0.5), 0 0 0 4px rgba(0,149,255,0.15);

Personally I would use a stronger (higher contrast) colour, for accessibility.

Tags:

Css

Focus