How Discord Implemented App-Wide Keyboard Navigation

When working on creating a complete keyboard navigation experience for Discord, using styling with :focus and outline, the folks at Discord ran into issues where the outline would not match the shape of actual element being rendered. Thinks like border-radius, overflow: hidden; on the container, padding got in the way. So they set out to find a solution.

After a lot of trial and error, we landed on a system which is built on two components: FocusRing for declaring where a ring should be placed, and FocusRingScope for declaring a root position for rings to be rendered.

Here’s an example showing how the FocusRing works:

function SearchBar() {
  const containerRef = React.useRef<HTMLDivElement>(null);
  const inputRef = React.useRef<HTMLInputElement>(null);
  return (
    <FocusRing focusTarget={inputRef} ringTarget={containerRef}>
      <div className={styles.container} ref={containerRef}>
        <input type="text" ref={inputRef} placeholder="Search" />
        <div className={styles.icon}>
          <ClearIcon />
        </div>
      </div>
    </FocusRing>
  );
}

The FocusRing will capture focussing of the contained input, but will render the ring around the entire div. To have a FocusRing behave like :focus-within and respond to any descendant being focussed, you can set the within prop.

The package can be installed using NPM:

npm i react-focus-rings

How Discord Implemented App-Wide Keyboard Navigation →
Browser Focus Ring Problems →
react-focus-rings (GitHub) →

Related: Not entirely coincidental the aforementioned React Spectrum by Adobe also comes with a FocusRing component.

Published by Bramus!

Bramus is a frontend web developer from Belgium, working as a Chrome Developer Relations Engineer at Google. From the moment he discovered view-source at the age of 14 (way back in 1997), he fell in love with the web and has been tinkering with it ever since (more …)

Leave a comment

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.