Take typical focus indicator styles and turn them totally tabbular!
Minimum Requirements
For an in depth breakdown of these requirements, check out Sara Soueidan's excellent guide for designing accessible, WCAG-compliant focus indicators.
Essentially, if an element is focused, it must be visible on the page and its focus indicator must have a color contrast ratio of 3:1 with surrounding colors. The minimum coverage area for the focus indicator is 1px thick around the entire perimeter of the element or 4px thick along the shortest side.
This leaves a lot of room for creativity - you can use outlines, background colors, borders, and shadows in a variety of ways. The world's your oyster as long as it's very obvious the state of the element has changed.
Special mention: errors
If a user is entering information into your page, they want clear errors. Usually, that means moving focus to the element that has an error. People generally expect a focus outline that indicates an error to be red.
First, always check your red for minimum color contrast. Second, color cannot be the only way you communicate information. You will need to provide text (not placeholder text) clearly describing what went wrong.
Check out Shell Little's Fix Your Error Flows or Give Me More Spoons axe-con talk for more dos and don'ts when designing error flows.
CSS Pseudo-classes
A pseudo-class is a keyword indicating a special state added to a CSS selector like .class-name:focus
or button:focus
.
:focus
This pseudo-class represents an element that has received focus by a click, tap, or the tab key.
:focus-visible
Browsers calculate when this state is active. We can generally assume this pseudo-class is activated when a user sets focus to "always visible" in their browser settings or the element is focused with the keyboard only.
:focus-within
Use this pseudo-class if you want to show a focus outline on an element when it or any of its children are focused. This little guy has already come in handy for me a couple of times, most notably when I was trying to create a dynamic skip link for a page displaying all of my blogs.
Focusing Fun
My two purples, #231942 and #6B65A4, both have WCAG AA small text minimum contrast with the white text and thus the background. They also have the minimum contrast ratio of 3:1 with each other.
Outlines
Manuel Matuzović covers the pros and cons of outlines in depth. The biggest pro is it won't cause layout shift. It's also the most universally supported CSS property in this blog.
All the other styles I demonstrate here will be overridden in forced color mode. In contrast, only the color of outlines will be removed. It is recommended that if you use other styles, you keep a transparent outline or an outline in the same color.
Many properties of an outline cannot be changed, like its border radius.
Let's start with the most basic outline.
button {
height: 40px;
width: 80px;
border-radius: 5px;
background-color: #231942;
border: none;
color: #FFFFFF;
}
button:focus {
outline: #6B65A4 solid 3px;
}
You can use the outline-offset
property to create an outline with space between it and the element.
button:focus {
outline: #6B65A4 solid 3px;
outline-offset: 5px;
}
With negative values, you can even put it inside the element.
button:focus {
outline: #6B65A4 solid 4px;
outline-offset: -8px;
}
Because the minimum requirement is 1px thick around the perimeter of the element, you must go thicker if you move the outline inside the element.
I'm only using solid here, but outline-style
has quite a few options. If you start using styles like dashed, you'll have to increase the width to ensure it still meets the minimum contrast area requirements.
Background Colors
The big benefit of background-color
is the large contrast area. It's also well supported.
button:focus {
outline: none;
background-color: #6B65A4;
}
Borders
Borders are well supported and the least likely to be cut off by overflow, but they have some quirks. For instance, borders will not stay with their element if it's positioned using sticky
and you start scrolling.
Like outline, there are plenty of border-style options, and you can even add a border-image.
To prevent layout shift (a visible element on your page changing position or size), your border must already exist before your element is focused. Luckily, if it's the same color as your background, it's not noticeable.
button {
border: 4px solid #231942;
}
Then you can change the whole border on focus.
button:focus {
outline: none;
border: 3px solid #6B65A4;
}
You can also change just one side, remembering it has to be thicker.
button:focus {
outline: none;
border-left: 4px solid #6B65A4;
}
To get a straight line, you can replace padding with the border:
button {
border: none;
padding-left: 4px;
}
button:focus {
outline: none;
padding-left: 0;
border-left: 4px solid #6B65A4;
}
Alternatively, you can use clip-path
.
button {
border: 4px solid #231942;
clip-path: inset(8px 0px);
}
You can't create an offset border on an element, but you can use an outline to fake an inset border. Any border-radius
creates odd gaps, but it prevents layout shift.
button {
outline: #231942 solid 3px;
}
button:focus {
border: 2px solid #6B65A4;
}
Box Shadows
All of this you can do with box-shadow
without causing layout shift. The downside is box-shadow
isn't as well supported, especially in older browsers and e-mail clients.
On the perimeter:
button:focus {
outline: none;
-webkit-box-shadow: 0px 0px 0px 2px #6B65A4;
-moz-box-shadow: 0px 0px 0px 2px #6B65A4;
box-shadow: 0px 0px 0px 2px #6B65A4;
}
Inside the perimeter:
button:focus {
outline: none;
-webkit-box-shadow:inset 0px 0px 0px 4px #6B65A4;
-moz-box-shadow:inset 0px 0px 0px 4px #6B65A4;
box-shadow:inset 0px 0px 0px 4px #6B65A4;
}
One side:
button:focus {
outline: none;
-webkit-box-shadow: -4px 0px 0px #6B65A4;
-moz-box-shadow: -4px 0px 0px #6B65A4;
box-shadow: -4px 0px 0px #6B65A4;
}
One side and inset:
button:focus {
outline: none;
-webkit-box-shadow: inset 4px 0px 0px #6B65A4;
-moz-box-shadow: inset 4px 0px 0px #6B65A4;
box-shadow: inset 4px 0px 0px #6B65A4;
}
box-shadow
conforms to your element's border-radius
, so you can get some weird looking curves when you're imitating solid lines with it.
Background color:
button:focus {
outline: none;
-webkit-box-shadow: inset 20px 40px 0px #6B65A4;
-moz-box-shadow: inset 20px 40px 0px #6B65A4;
box-shadow: inset 20px 40px 0px #6B65A4;
}
For more box-shadow
fun, check out Mozilla Developer Network's Box-shadow Generator!
Conclusion
Look, most people hate the default focus indicator. You should never remove it entirely, but you can provide nice-looking, visible focus indicators with a little CSS!
Thanks for taking the time to read this tabbing tutorial!
Top comments (1)
That's a nice and helpful series, I love how detailed it is. Thank you!