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});


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.

Smooth Scrolling Sticky ScrollSpy Navigation

Yesterday evening I was working on a documentation page. The page layout is quite classic, as it consists of a content pane on the left and a sidebar navigation on the right. Looking for a way to make the page less dull I decided to add a few small things to it:

  1. Smooth Scrolling when clicking internal links
  2. A Sticky Navigation, so that the sidebar navigation always stays in view
  3. A “ScrollSpy” to update the active state of the navigation

These three additions make the page more delightful, and best of all is: they’re really easy to implement!

In this post I’ll lay out the details.


# The result

First things first, here’s a recording of the end result so that you get an idea of what I’m talking about:


# The Markup

The markup is really basic:

  • A main element surrounds our content div and nav.
  • Each piece of content is wrapped in a section which gets an id attribute. The sidebar navigation then links to its id
		<h1>Smooth Scrolling Sticky ScrollSpy Navigation</h1>
		<section id="introduction">
		<section id="request-response">
			<h2>Request &amp; Response</h2>
		<section id="authentication">
		<section id="filters">
	<nav class="section-nav">
			<li><a href="#introduction">Introduction</a></li>
			<li><a href="#request-response">Request &amp; Response</a></li>
			<li><a href="#authentication">Authentication</a></li>
			<li class=""><a href="#filters">Filters</a></li>

Sprinkle some CSS on top to lay everything out – using CSS Grid here – and you have a fully working – albeit dull – page:

See the Pen
Smooth Scrolling Sticky ScrollSpy Navigation (base layer)
by Bramus (@bramus)
on CodePen.


# 1. Smooth Scrolling

Enabling smooth scrolling is really easy, it you can enable it using a single line of CSS:

html {
	scroll-behavior: smooth;

😱 Yes, that’s it!

In the demo embedded below, click any of the links in the nav and see how smooth it scrolls:

See the Pen
Smooth Scrolling Sticky ScrollSpy Navigation (base layer)
by Bramus (@bramus)
on CodePen.

For browsers that don’t support this you could add this JS fallback:

// Smooth scrolling for browsers that don't support CSS smooth scrolling
if (window.getComputedStyle(document.documentElement).scrollBehavior !== 'smooth') {
    document.querySelectorAll('a[href^="#"]').forEach(internalLink => {
        const targetElement = document.querySelector(internalLink.getAttribute('href'));
        if (targetElement) {
            internalLink.addEventListener('click', (e) => {
                    behavior: 'smooth',

However, browser’s that don’t support scroll-behavior: smooth; also don’t support behavior: "smooth" for Element#scrollIntoView, so there’s not real advantage to adding this JS fallback.


# 2. Sticky Navigation

To make the navigation stay in place as you scroll we can rely on position: sticky;. As with Smooth Scrolling, this is a really simple CSS addition:

main > nav {
	position: sticky;
	top: 2rem;
	align-self: start;

💁‍♂️ Since we’re using CSS Grid to lay out the children of <main>, adding align-self: start; to <nav> is an important one here. If we would omit it, the nav element would be as high as the enclosing main element. If that were the case, then nav would never be able to stick.

In the demo embedded below, click any of the links in the nav and see how the nav now also stays in view while the rest of the page scrolls:

See the Pen
Smooth Scrolling Sticky ScrollSpy Navigation (base layer + smooth scrolling + sticky nav)
by Bramus (@bramus)
on CodePen.


👋 Like what you see so far? Follow @bramusblog on Twitter or subscribe to the RSS feed to stay up-to-date.


# 3. ScrollSpy with IntersectionObserver

Update 2021.07.19: Thanks to CSS @scroll-timeline we can now also implement a ScrollSpy using only CSS! See my post up on CSS-Tricks to get the details.

Thanks to the almighty IntersectionObserver we can implement a ScrollSpy. Basically we use it to watch all section["id"] elements. If they are intersecting, we add the .active class to any link that links to it. For styling purposes we don’t add .active to the link itself, but to its surrounding li element.

In code, that becomes this little snippet:

window.addEventListener('DOMContentLoaded', () => {

	const observer = new IntersectionObserver(entries => {
		entries.forEach(entry => {
			const id = entry.target.getAttribute('id');
			if (entry.intersectionRatio > 0) {
				document.querySelector(`nav li a[href="#${id}"]`).parentElement.classList.add('active');
			} else {
				document.querySelector(`nav li a[href="#${id}"]`).parentElement.classList.remove('active');

	// Track all sections that have an `id` applied
	document.querySelectorAll('section[id]').forEach((section) => {

💡 To make the transition to and from .active not too abrupt, add a little blob of CSS to ease things:

.section-nav a {
    transition: all 100ms ease-in-out;


# Complete Demo

Putting everything together, we end up with this:

See the Pen
Smooth Scrolling Sticky ScrollSpy Navigation
by Bramus (@bramus)
on CodePen.

Delightful, no? 😊


💡 If you’re also looking for more inspiration to make your interfaces more delightful, be sure to check Hakim El Hattab’s “Building Better Interfaces” talk. Recommended stuff!

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.

Pure CSS Scroll Shadows (Vertical + Horizontal)

A long time ago (2012!), Lea Verou shared a way on how to add scrolling shadows to containers that needs scrolling. Using those shadows in a scroll container is a great UX thing, as they visually tell the user that the content is scrollable.

Her code however, only worked with containers that scroll vertically. Based upon Lea’s code I created a version that also plays nice with horizontal scroll containers.

Whilst I was at it, I also introduced CSS Custom Properties, as they allow easy theming. Using the “CSS Variables” --background-color, --shadow-color, and --shadow-size you can configure the result.

Hope you like it 🙂

# 🐛🍏 A note on MobileSafari (iOS)

Update 2021.10.06: The workaround listed below no longer works since iOS15 😭

Out of the box this technique unfortunately does not play that nice on iOS: MobileSafari does not seem to re-render the backgrounds during scroll. I did notice however that it will correctly re-render after manually zooming in and out on the page. This is detailed in Webkit Bug 181048.

In said bug report a solution to the problem is mentioned:

I’ve discovered that updating a CSS variable on a -webkit-overflow-scrolling: touch; element when it’s scrolled causes it to repaint correctly.

With a little bit of JavaScript we can trigger exactly this:

// Fix for Mobile Safari that doesn't correctly re-render backgrounds during scroll
// @ref https://bugs.webkit.org/show_bug.cgi?id=181048
if (CSS.supports("-webkit-overflow-scrolling: touch")) {
  document.querySelectorAll(".scrollcontainer").forEach((scroller) => {
    scroller.addEventListener("scroll", () => {
      scroller.style.setProperty("--force-paint", Date.now());

It’s also possible to force repaints by attaching an animation to the .scrollcontainer. Even a dummy (empty) animation will do.

/*Fix for Mobile Safari that doesn't correctly re-render backgrounds during scroll */
@supports (-webkit-overflow-scrolling: touch) {
  @keyframes dummy {
  .scrollcontainer {
    animation: 1s dummy linear infinite;
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.

Building Better Interfaces, a talk by Hakim El Hattab

At CSS Day 2019, I was fortunate to see Hakim El Hattab bring his talk Building Better Interfaces. A recording of his talk – and all other talks – are available on YouTube:

This session is a deep-dive into the areas of interface design that Hakim has specialized in for over a decade—interactivity and animation. He’s going to take an in-depth look at a few different UI components to highlight common pitfalls and present concrete solutions. He’ll talk about the correlation between having fun at work and building interfaces that are fun to work with. Most of all, he’s going to share practical tips for building better and more enjoyable user interfaces on the web.

Personally I think a more fitting title would’ve been “Building More Delightful Interfaces”, as the additions don’t necessarily make the interface itself better, as they already were really good to begin with. If you have the time to add these kind of extras, be sure to do so … they surely will bring smiles on the face of your users.

An HTML attribute potentially worth $4.4M to Chipotle

Jason Grigsby:

I recently found myself racing to fill out Chipotle’s online order form before my mother could find her credit card. In the process, I discovered a bug that could cost Chipotle $4.4 million annually.

The form didn’t play nice with autocomplete and therefore would not get sent …

The culprit? A JS library which enforced a 2-digit pattern on the card details’ year field, thus truncating the autocompleted value (202320).

The solution? The pattern attribute, which the autocomplete feature can detect, thus passing in a correctly shortened valued (202323)

An HTML attribute potentially worth $4.4M to Chipotle →

Button Placement in Forms

Button placement on forms is often ignored or prioritised based on what looks good.

But button placement can make or break a form, and a form can make or break a user’s experience. That’s why it’s essential to get it right.

Here I’ll explain where to put buttons across a range of different forms based on research and best practice.

Where to put buttons on forms →

🤔 I found this one highly interesting to read as I tend to do the exact opposite when it comes to placing buttons. Inspired by macOS I place the primary button on the right, the secondary button to the left of the primary one, and the cancel option at the very left.

I’m wondering what Johan (Interface Designer) and Roel (Digital Accessibility Nerd) their take is on this …

UPDATE 2019.09.18: Thanks to (requested) input by Johan/Roel/Xavier we can safely conclude that the mentioned guidelines are not perfect.

As Johan mentions in the comments below:

I agree with your take mentioned in this blog post.

When there is only a single submit at the bottom of a typical form I do agree with aligning it left (in the linked post).

Roel also chimes in on placing the primary button on the right as all OS conventions do so. He however does wonder:

But, then again: web forms are not dialog windows…

As Xavier mentions on Twitter:

It doesn’t matter how your primary button is aligned. What matters is where all of the other buttons are relative to your primary action & inputs.

I think the main gist is that:

  • As the OS places primary actions on the right (in a left-to-right language that is), it’s a good idea to do so to as users are used to that.
  • If you do diverge from it, be consistent (cfr. Design Principle #9)

Thanks for the input y’all!

Hierarchic Indeterminate Checkboxes with JavaScript (Vanilla)

Earlier today Chris Coyier tweeted that he was in the process of rewriting one of his pens without jQuery:

Sparked by his tweet I decided to fork his pen and try to do it myself. Here’s the result:

A fun exercise if I say so myself, even though I’m not 100% satisfied with the manual traversal I needed to do (.parentElement.parentElement — really?). Perhaps there’s a better way to do this?

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.

Paint Holding in Google Chrome

One of the features that shipped with Chrome 76 is “Paint Holding”. It’s a technique that removes the “flash of white” – e.g. the white page you briefly see while the browser is loading the next page – when navigation between two pages on the same origin, thus delivering a smoother experience to the user.

The way this works is that we defer compositor commits until a given page load signal (PLS) (e.g. first contentful paint / fixed timeout) is reached. Waiting until a PLS occurs reduces likelihood of flashes of white/solid-color.

No idea what the fixed timeout is, but let it be another reason make your site as fast as hell 🙂

Paint Holding – reducing the flash of white on same-origin navigations →