:focus-visible Is Here

With Chromium 86 and now recently Firefox 85 supporting :focus-visible, it’s a good time to refer to this post by Matthias Ott:

The :focus-visible pseudo-class lets you show focus styles only when they are needed, using the same heuristic that the browser uses to decide whether to show the default focus indicator.

You use :focus-visible just like you use :focus. To properly hide focus rings on “mouse focus” though, you’ll need to add an extra rule-set in which you combine :focus with (a negated) :focus-visible:

/* Show focus styles on keyboard focus. */
:focus-visible {
  outline: 3px solid blue;
}

/* Hide focus styles if they're not needed, for example, 
when an element receives focus via the mouse. */
:focus:not(:focus-visible) {
  outline: 0;
}

💁‍♂️ This extra rule-set is needed because browser vendors add a default outline using :focus through their User Agent StyleSheet.

In the (near?) future this extra rule-set will no longer be needed as the HTML spec now prescribes that UA StyleSheets use :focus-visible to add the default outline.

Awaiting the work Igalia is doing on this, Safari does not yet support :focus-visible, but in the meantime you can use a polyfill.

:focus-visible Is Here →
focus-visible Polyfill →

Form Validation: You want :not(:focus):invalid, not :invalid

Update 2021-01-28: In case your form controls have the required attribute set, you’ll even want to use the more extensive :not(:focus):not(:placeholder-shown):invalid selector.

See the update at the end of this post for more info on this.

We’ve all been in this situation, where the built-in form validation of the browser starts complaining while you’re still entering data into the form:

Highly annoying, but thankfully there’s an easy way to fix this.

~

# The problem

The problem is caused by a piece of CSS similar to this snippet:

.error-message {
    display: none;
}

input:invalid {
  border-color: var(--color-invalid);
}

input:invalid ~ .error-message {
  display: block; 
}

input:valid {
  border-color: var(--color-valid);
}

When entering an e-mail address — as Ryan is doing above — this is extremely annoying as your e-mail address is only valid when you’re done entering it. Try it in the demo below.

See the Pen
Form Validation on Blur (1/4)
by Bramus (@bramus)
on CodePen.

Ugh! 🤬

💁‍♂️ For a slight moment you’ll notice that an e-mail address in the form of bramus@bram (e.g. without a .tld suffix) is also considered valid. As per RFC 822 the user@hostname format — used mainly in local networks — indeed is allowed.

~

# Making things better

It would be nice to only perform the validation when the field is not being edited anymore. In CSS we don’t have a blur event, but what we do have is a pseudo-class selector to indicate whether an input has the focus: :focus. Combine that with :not() and we have a way to target the “not being focussed” state, which also indicates that the field is not being edited.

Putting it all together, our CSS becomes this:

.error-message {
    display: none;
}

input:not(:focus):invalid {
  border-color: var(--color-invalid);
}

input:not(:focus):invalid ~ .error-message {
  display: block; 
}

input:not(:focus):valid {
  border-color: var(--color-valid);
}

This way the validations only happen when you’re blurred out of the form.

See the Pen
Form Validation on Blur (2/4)
by Bramus (@bramus)
on CodePen.

Ah, that’s better! 😊

~

# Making things even more better

In the demo above you’ll see one small side-effect though: the border is green by default, even though we didn’t enter any value. This is not exactly what we want Ideally we only want to validate in case the field is both not focussed and not empty.

In CSS we can’t use :empty for this though, as :empty targets elements that have no children/innerHTML content. What we can do however is abuse the :placeholder-shown pseudo-class.

  • If there’s no text entered, the placeholder is shown
  • If there is text entered, the placeholder is not shown

With this in mind, our code now becomes this:

.error-message {
    display: none;
}

input:not(:focus):invalid {
  border-color: var(--color-invalid);
}

input:not(:focus):invalid ~ .error-message {
  display: block; 
}

input:not(:focus):not(:placeholder-shown):valid {
  border-color: var(--color-valid);
}

⚠️ Do note that this requires a value for the input‘s placeholder.

<input type="email" placeholder="you​@​example​.​org" />

If you don’t want any placeholder to show, set its value to   (space)

Here’s an adjusted demo:

See the Pen
Form Validation on Blur (3/4)
by Bramus (@bramus)
on CodePen.

Yes, exactly what we want! 🤩

Note: In the future we will be able to use the :user-invalid pseudo class for exactly this use-case.

The :user-invalid pseudo-class represents an element with incorrect input, but only after the user has significantly interacted with it.

This feature is still in the works and not supported yet. Firefox supports it using the non-standard ::-moz-ui-invalid name. Thanks for the tip, Schepp!

~

# Update 2021-01-28: Playing nice with required

As reader Corey pointed out in the comments below the code above does not play nice with the required attribute. When the attribute is added, the error message will be shown when the form loads.

To work around this we also need to include the :not(:placeholder-shown) pseudo-class in our :invalid selectors.

.error-message {
    display: none;
}

input:not(:focus):not(:placeholder-shown):invalid {
  border-color: var(--color-invalid);
}

input:not(:focus):not(:placeholder-shown):invalid ~ .error-message {
  display: block; 
}

input:not(:focus):not(:placeholder-shown):valid {
  border-color: var(--color-valid);
}

Putting it all together, here’s our final demo:

See the Pen
Form Validation on Blur (4/4)
by Bramus (@bramus)
on CodePen.

Phew! 😅

~

🔥 Like what you see? Want to stay in the loop? Here's how:

Note: The title of this post is definitely a reference to this post on CSS-Tricks and this post by Kilian.

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.

Clicking Buttons: Inconsistent behavior across browsers

Great research by Zell Liew:

I noticed browsers were inconsistent in how they handle a click on <button>. Some browsers choose to focus on the button. Some browsers don’t.

In this article, I want to show you my test and findings. Then, I want to talk about a way to overcome these inconsistencies.

Great to see all those illustration gifs to show what’s going one. Must’ve taken a ton of work to create them all.

Inconsistent behavior among browsers when clicking on buttons →

Indicating focus to improve accessibility

Great article by Hidde. It totally rhymes with my Building Better Forms™ by not taking away affordances post.

It’s a common, but fairly easy-to-fix accessibility issue: lack of indicating focus. In this post I will explain what we mean by focus and show you how focus outlines make your site easier to use.

Indicating focus to improve accessibility →

Spectacle CodeSlide: Present code with style

spectacle-code-slide

Present code with style using spectacle.

Awesome way to present code, and putting the focus of the reader where it needs to be. It also lets you jump to specific lines even if they’ve already been displayed before (because code tends to jump).

import React from 'react';
import { Spectacle, Deck } from 'spectacle';
import CodeSlide from 'spectacle-code-slide';
import shiaLabeoufMagicGif from "./shiaLabeoufMagic.gif"
import preloader from "spectacle/lib/utils/preloader";

preloader({
  shiaLabeoufMagicGif
});

export default class Presentation extends React.Component {
  render() {
    return (
      <Spectacle theme={theme}>
        <Deck transition={[]} transitionDuration={0} progress="bar">
          // ...
          <CodeSlide
            transition={[]}
            lang="js"
            code={require("raw!../assets/code.example")}
            ranges={[
              { loc: [0, 270], title: "Walking through some code" },
              { loc: [0, 1], title: "The Beginning" },
              { loc: [1, 2] },
              { loc: [1, 2], note: "Heres a note!" },
              { loc: [2, 3] },
              { loc: [4, 7], image: shiaLabeoufMagicGif },
              { loc: [8, 10] },
              // ...
            ]}/>
          // ...
        </Deck>
      </Spectacle>
    );
  }
}

Spectacle CodeSlide (GitHub) →
Spectacle CodeSlide Demo →

In the past – when I was still working in education – I worked with snippets that fade in when advancing, or code that is executed inline. Spectacle CodeSlide would’ve been a great addition to these two techniques, and could’ve formed a replacement for the laser pointer.

We’re not ‘appy. Not ‘appy at all.

The British Government Digital Service (a new team within Cabinet Office, which in its turn supports the Prime Minister and Deputy Prime Minister) is totally getting it:

Stand-alone mobile apps will only be considered once the core web service works well on mobile devices, and if specifically agreed with the Cabinet Office.

Above that:

Apps may be transforming gaming and social media, but for utility public services, the ‘making your website adapt really effectively to a range of devices’ approach is currently the better strategy. It allows you to iterate your services much more quickly, minimises any market impact and is far cheaper to support.

With that, they’re pushing anyone within the central government to, let me quote:

make your data and/or API available for re-use and you will stimulate the market if there is demand for native apps

Highly recommended read. Also contains a solid presentation (embedded above) and some figures of website performance after having given some a (responsive) overhaul. Spoiler: performance increased.

We’re not ‘appy. Not ‘appy at all. →

The British Government surprised me before, with their list of Design Principles. Also highly recommended.