: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 [email protected] (e.g. without a .tld suffix) is also considered valid. As per RFC 822 the [email protected] 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.

“Yes or No?” — One Checkbox vs Two Radio Buttons

Great research by Sara Soueidan:

If you have a question with a binary Yes/No answer, is it better to use one checkbox or two radio buttons?

As per usual: It Depends™. The consensus however seems to be that requiring an explicit “No” is the determining factor to avoid the simple checkbox.

Over at CSS-Tricks Chris Coyier has collected several options how to implement this, in one handy pen:

See the Pen Ways to ask Yes or No by Chris Coyier (@chriscoyier) on CodePen.

Do note that not all of those listed options are as usable as the others. As Sara says:

[A]s with all user interfaces, nothing beats the input you can get from user testing and research.

“Yes or No?” — One Checkbox vs Two Radio Buttons →

Avoiding tab styles for navigation

Adam Silver, who works at/with the fine folks at GOV.UK:

Tabs should only look like tabs if they behave like tabs otherwise it can be in disorienting and confusing for users.

Shown above is the old layout that featured the tabs (which are actually links, here). The new version still has the links in place at the same position, but they’ve styled them differently so that the look less like tabs.

Avoiding tab styles for navigation →

8 Tips to Make Your Website Feel Like an iOS App

Very nice video by Sam Selikoff in which he sets up a web manifest to make his site feel more app-like. Great production quality.

There are some tweaks I’d suggest though:

  1. Fixate the header using position: sticky; instead of position: fixed;. No need for that extra margin on the content then. Update: See note below

  2. Don’t set the header height to a fixed number, as not all devices have (equally sized) notches. Use the User Agent Variable safe-area-inset-top instead, and apply it as the top padding:

    header {
      padding-top: env(safe-area-inset-top);
    }
  3. Don’t disable scaling in case your app does not follow the system font sizing — which it does not by default. To make it do follow the system font sizing, use this snippet:

    @supports (font: -apple-system-body) {
      html {
        font: -apple-system-body;
      }
    }

    💡 You can still override the font-family after that. That way you can have your custom font and also follow the preferred system Text Size

As an extra: to prevent that long-press behavior when the app is not added to the home screen, set -webkit-touch-callout: none; on the links.

On Twitter Sam pointed me out that when using position: sticky; there’s an issue with overscroll/bounce: The header will shift down along with the body as you do so.

In theory one could easily fix this by applying some overscroll-behavior on the body. However, as Safari does not support overscroll-behavior you’ll have to resort to a hack to prevent overscrolling in Safari. But, as that hack relies on 100vh — which Safari also doesn’t understand properly — you’ll have to use another hack to make Safari use the correct value for 100vh.

(* mumbles something about Safari being the new IE6 *)

Did this help you out? Like what you see?
Thank me with a coffee.

I don't do this for profit but a small one-time donation would surely put a smile on my face. Thanks!

☕️ Buy me a Coffee (€3)

To stay in the loop you can follow @bramus or follow @bramusblog on Twitter.

HTML Forms: How (and Why) to Prevent Double Form Submissions

When double clicking a submit button your form will be sent twice. Using JavaScript you can prevent this from happening, but wouldn’t it be nice if this behavior could be tweaked by use of an attribute on the <form>? If you think so, feel free to give this issue a thumbs up.

Today Sebastian wondered:

I quickly chimed in saying that I do tend to lock up forms when submitting them. Let me explain why …

~

I started locking up submitted forms as users of the apps I’m building reported that sometimes the actions they had taken — such as adding an entry — were performed twice. I took me some time to figure out what exactly was going on, but eventually I found out this was because they were double clicking the submit button of the forms. As they double clicked the button, the form got sent over twice. By locking up forms after their first submission, all successive submissions — such as those triggered by that second click of a double click — are ignored.

~

To prevent these extra form submissions from being made I don’t hijack the form with Ajax nor do I perform any other complicated things. I let the inner workings of the web and forms in the browser be: when pressing the submit button the browser will still collect all form data, build a new HTTP request, and execute that request.

What I simply do is extend the form’s capabilities by adding a flag — by means of a CSS class — onto the form to indicate whether it’s being submitted or not. I can then use this flag’s presence to deny any successive submissions, and also hook some CSS styles on. — Progressive Enhancement, Yay! 🎉

The code looks as follows:

 // Prevent Double Submits
document.querySelectorAll('form').forEach(form => {
	form.addEventListener('submit', (e) => {
		// Prevent if already submitting
		if (form.classList.contains('is-submitting')) {
			e.preventDefault();
		}
		
		// Add class to hook our visual indicator on
		form.classList.add('is-submitting');
	});
});

💡 Although the problem initially was a double click problem, we don’t listen for any clicks on the submit button but listen for the form’s submit event. This way our code not only works when clicking any of the submit buttons, but also when pressing enter to submit.

With that .is-submitting class in place, we can then attach some extra CSS onto the form to give the user visual feedback. Here’s a few examples:

See the Pen
Prevent Form Double Submits
by Bramus (@bramus)
on CodePen.

See the Pen
Prevent Form Double Submits (Alternative version)
by Bramus (@bramus)
on CodePen.

Note that this solution might not cover 100% of all possible scenarios, as it doesn’t take failing networks and other things that might go wrong into account. However, as I’m relying on the basic mechanisms of the web I think I can also rely on the browser to show that typical “Confirm Form Resubmission” interstitial should a timeout occur. Additionally, if need be, one could always unlock the form after a fixed amount of time. That way the user will be able to re-submit it again.

~

Dealing with double form submissions isn’t a new issue at all. You’ll find quite some results when running a few queries through Google — something I only found out after stumbling over the issue myself.

Back in 2015 (!) Mattias Geniar also pondered about this, after being pulled into the subject from a sysadmin view. Now, when even sysadmins are talking about an HTML/UX issue you know there’s something going on. This made me wonder why browsers behaved like that and how we could solve it:

As a result I decided to open an issue at the WHATWG HTML Standard repo, suggesting for a way to fix this at spec level:

An attribute on <form> to tweak this behavior – instead of having to rely on JavaScript – would come in handy and form a nice addition to the spec.

I see two options to go forward:

  1. Browsers/the standard keeps the current behavior and allow multiple submits. Developers must opt-in to prevent multiple submissions using a preventmultiplesubmits attribute.
  2. Browsers/the standard adjust the current behavior to only submit forms once. Developers must opt-in to allow multiple submissions using a allowmultiplesubmits attribute.

Initial response on the issue was very low, and it looks like this isn’t that big of a priority.

Back then I was more in favor of the second solution, but now I’m quite undecided as changing the default behavior — which has been around for ages — is quite tricky.

~

Another way that this issue could be fixed is at the browser level: if they were to treat double clicks on form submit buttons as single clicks, then the double click problem — but not the double submit problem — could also be taken care of.

To then attach styles to forms being submitted a CSS Pseudo Class :submitting would come in handy. Come to think of it, this Pseudo Class would be a quite nice addition to CSS in itself, no matter whether this double click issue gets solved or not.

~

Winging back to the addition to the HTML spec I suggested: If you do think it could be something the HTML spec could benefit from, feel free to add a thumbs up to the issue to indicate that you want this, or add an extra comment to it if you have more input on the subject.

Did this help you out? Like what you see?
Thank me with a coffee.

I don't do this for profit but a small one-time donation would surely put a smile on my face. Thanks!

☕️ Buy me a Coffee (€3)

To stay in the loop you can follow @bramus or follow @bramusblog on Twitter.

Enhancing User Experience With CSS Animations

Great talk by Stéphanie Walter (starting at 39:00):

With practical examples, I show why certain animations work better than others and how to find the best timing and duration to build UI animations that “feel right”. I explain how our brain works, why and how animations contribute to improving user experience. And what you need to be careful about to build inclusive and accessible motion and avoid making some people sick.

Be sure to check the post on her website too, as it contains a full writeup, videos, examples, slides, links to extra resources, etc.

Enhancing User Experience With CSS Animations →

Web Vitals – Essential metrics for a healthy site

Web Vitals is a new great set of articles on Web.dev (by Google) focussing on delivering a great user experience on the web. To help developers focus on what matters most, they’ve selected a set of metrics which they call “Core Web Vitals”.

The metrics that make up Core Web Vitals will evolve over time. The current set for 2020 focuses on three aspects of the user experience — loading, interactivity, and visual stability — and includes the following metrics (and their respective thresholds):

  • Largest Contentful Paint (LCP): measures loading performance. To provide a good user experience, LCP should occur within 2.5 seconds of when the page first starts loading.
  • First Input Delay (FID): measures interactivity. To provide a good user experience, pages should have a FID of less than 100 milliseconds.
  • Cumulative Layout Shift (CLS): measures visual stability. To provide a good user experience, pages should maintain a CLS of less than 0.1.

The easiest way to quickly see these metrics is to use the Web Vitals Chrome Extension.

To get data from your visitors there’s the web-vitals library that you can use to store the data yourself (or in Google Analytics if you’re using that)

import {getCLS, getFID, getLCP} from 'web-vitals';

function sendToAnalytics(metric) {
  const body = JSON.stringify(metric);
  // Use `navigator.sendBeacon()` if available, falling back to `fetch()`.
  (navigator.sendBeacon && navigator.sendBeacon('/analytics', body)) ||
      fetch('/analytics', {body, method: 'POST', keepalive: true});
}

getCLS(sendToAnalytics);
getFID(sendToAnalytics);
getLCP(sendToAnalytics);

The best part of the whole Web Vitals series is that not only they tell you what the important metrics are, they also tell you what you can do to score better on each individual metric.

Once you’ve measured the Core Web Vitals and identified areas for improvement, the next step is to optimize. The following guides offer specific recommendations for how to optimize your pages for each of the Core Web Vitals:

Those three articles are packed with solid and actionable advice. Every web developer should read this. Twice.

Web Vitals →

Guidelines for text fields design

Text fields are probably one of the most used interface components; contact forms, payment details, account creation, lead generation. Users definitely need to interact with text fields when using your product. The purpose of this post is to highlight some of the key guidelines which I follow when it comes to designing better text fields.

It’s pretty basic things that, unfortunately, a lot of people seem to forget.

Guidelines for text fields design →

💵 This linked article is stuck behind Medium’s metered paywall, which may prevent you from reading it. Open the link in an incognito window to bypass Medium’s ridiculous reading limit.