Many people who operate their devices with a keyboard need a visual indicator of keyboard focus. Without a visual indicator of which element has focus, it's hard to know what, say, hitting enter might do. Anything might happen!It's reasonable to think that either the browser or the operating system should be in charge of making sure keyboard focus is perceivable to keyboard users. You might think that it's best for devs not to overwrite focus styles at all. However, if your website is customizing the look of inputs, there's a good chance that the default focus ring won't actually be visible to your users.This is the situation we found ourselves in at NoRedInk, and what follows is what we did to improve things.The ProblemBordersAt NoRedInk, both our primary and secondary buttons feature blue (specifically, #0A64FF) borders. On a Mac using Chrome, this particular blue color on the edge of the button essentially makes the default focus rings invisible.Can you tell the difference between these two pictures? The second one has a focus ring, but if the pictures weren't side by side, there's no way you would know.Okay, you might be thinking, don't have blue buttons. However, changing the button color, even if it were feasible from a branding standpoint, doesn't necessarily solve the problem. The outline color that's used for the default focus ring depends on the browser stylesheet, the operating system, and user settings.Personally, I have my macOS accent color set to pink, which results in a focus ring that disappears against NoRedInk's danger button style.BackgroundsEven if we could customize all of our buttons, inputs, and components so that the border and the focus ring outline always showed cleanly against each other, we still wouldn't have solved the entire problem.This is because there's not only the question of the focus indicator showing up against an input to consider: we also need to consider the contrast of the focus indicator against the background color of an input's container.The importance of taking the background color into account became more apparent to us at NoRedInk when we started working on a redesign of NoRedInk's logged out home page that made heavy use of blue and navy backgrounds.Some browsers have implemented two-toned focus rings that will show up clearly on different backgrounds, but there are major browsers that haven't.Here are two screenshots of the same link being actively focused. The first screenshot, taken in Chrome, shows a focus indicator. The second screenshot, taken in Safari, shows only how a focus indicator can become truly and totally indistinguisable from a background.Problem summaryIf we are customizing input styles, we probably also need to make sure that our focus ring (1) has enough contrast with the edge color of the input and (2) has enough contrast with the input's container's background color.ApproachWhile we could have customized the focus ring color for every input and background color individually, we worried that having the focus indicator appear differently in different contexts would make it harder for folks to understand the meaning of the indicator.Consistency in UX is really important for usability in general. We didn't want a user to ever have to hunt for their focus. Keeping the focus indicator colors consistent and vivid helped us achieve this goal, and using a two-tone indicator allowed us to have a familiar look & feel everywhere.Reducing cognitive load is also important for usability: folks who don't use the keyboard for most of their interactions shouldn't be distracted by a weird, bright ring that follows them around as they interact.The Accessibility Team's designer, Ben Dansby, crafted a high-contrast two-toned focus ring that would appear only for users whose last interaction with the application indicated that they were keyboard users.Ben used red (#e70d4f) and white (#ffffff) for the two tones. These colors don't strictly guarantee sufficient contrast for all possible inputs, but it's straightfoward to check that our specific color palette will work well with these specific focus ring colors.Ellie with elm-charts code that produced the diagramLearn more about contrast requirements in the WebAIM article "Contrast and Color Accessibility".ImplementationThe two-toned focus ring Ben made used box-shadow to create a red and white outline with gently curved corners:[ Css.borderRadius (Css.px 4)
, Css.property "box-shadow" <|
"0 0 0px 3px "
++ toCssString Colors.white
++ ", 0 0 0 6px "
++ toCssString Colors.red
]We want the focus ring to only show for keyboard users, so we use the :focus-visible pseudoselector when attaching these styles.However, :focus-visible will result in the focus ring showing for text areas and text inputs regardless of whether the user last used a key for navigation or the mouse for navigation.We wanted keyboard users' text input focus clearly indicated with the new candy-cane bright indicator alongside our usual subtle blue focus effect.Blurred text input: Text input focused by a click: Text input focused by a key event: This required a more involved approach, beyond just using :focus-visible and changing the box-shadow.We needed to keep track of the last event type manuallyWe needed to not overwrite the box-shadow for text input when showing the focus ringTo accomplish the first of these goals, we stored a custom type type InputMethod = Keyboard | Mouse on the model and used Browser.Events.onKeyDown and Browser.Events.onMouseDown to set the InputMethod. We used different styles based on the InputMethod. Since we didn't want, say, arrow events within a textarea to change the user's InputMethod, we also added some light logic based on the tagName of the event target.For the second of these goals, we needed to be able to customize focus ring styles for inputs that already had box-shadow styles. This work needed to happen one component at a time.For example, styles for the text input might be applied as follows:forKeyboardUsers : List Css.Global.Snippet
forKeyboardUsers =
[ Css.Global.class "nri-input:focus-visible"
[ [ "0 0 0 3px " ++ toCssString Colors.white
, "0 0 0 6px " ++ toCssString Colors.red
, "inset 0 3px 0 0 " ++ toCssString Colors.glacier
]
|> String.join ","
|> Css.property "box-shadow"
, ...
]
, ...
]
forMouseUsers : List Css.Global.Snippet
forMouseUsers =
[ Css.Global.everything [ Css.outline Css.none ]
, Css.Global.class "nri-input:focus-visible"
[ Css.property "box-shadow" ("inset 0 3px 0 0 " ++ toCssString Colors.glacier)
, ...
]
, ...
]Now we have nice focus styles for keyboard users, nice focus styles for mouse users, as well as nice blurred styles! Of course, these are just the styles for our text input. There are lots more components to consider!This is the kind of change where having a library of example uses of every shared component becomes super useful. Having one view to go to to check all the focus rings makes it straightforward - although tedious - to make sure that the focus ring will look great everywhere.For us, we discovered that we needed a "tight" focus ring as well, where the box shadow is more inset, for cases where the ring would otherwise overlap other important content. [ Css.borderRadius (Css.px 4)
, Css.property "box-shadow" <|
"inset 0 0 0 2px "
++ toCssString Colors.white
++ ", 0 0 0 2px "
++ toCssString Colors.red
]We found also that some of our components already had border radiuses, and changing the border radious to 4px so that the focus ring would be nicely rounded was worse than keeping the initial border radius. This meant more per-component customization!Removing the outlineYou may have noticed that so far, none of the code samples for keyboard styles have actually hidden the default browser outline focus indicator. This is an area where we initially made an error: we naively added outline: none, thinking that our fancy new two-toned box-shadow-based focus ring would suffice.We were wrong!We forgot to consider and failed to test cases where users are in OS-based high contrast modes. High contrast modes essentially limit the colors that users are shown - the mode is not inherently high contrast, since the user can customize the palette that is used - by removing extraneous styling and forcing styles to match the given palette.Guess what counts as extraneous? The box-shadow comprising our two-toned focus ring!And if you set outline: none, the outline will not show in high contrast mode either.Instead of setting outline: none, we need to change the outline to be transparent for keyboard users: Css.outline3 (Css.px 2) Css.solid Css.transparent. The transparent color will (perhaps surprisingly) be coerced to a real color in high contrast mode.Summing it upCustomizing the look of a focus indicator can make it more useful for users, but it can take a surprising amount of work to get it just right. This work will be easier if you have a centralized place to see every common focusable element from your application at once. The two-toned focus ring in particular is great if your application has content over many different colored backgrounds, but it will be harder to implement if you commonly use box-shadows to accentuate inputs. Don't forget to consider and test high-contrast mode!Relevant resourcesWCAG 2.1 Understanding Success Criterion 2.4.7: Focus VisibleWCAG 2.1 Technique C4: Creating a two-color focus indicator to ensure sufficient contrast with all componentsTessa Kelly@t_kelly9
NoRedInk is a California-based digital writing platform that provides solutions such as interest-based curriculum, adaptive exercises and actionable data to students.