Smooth Scrolling Sticky ScrollSpy Navigation, with CSS fixed backgrounds

Davor Suljic created a fork of my Smooth Scrolling Sticky ScrollSpy Navigation that — instead of using IntersectionObserver — uses some background magic to highlight the active navigation items. The result is a “Pure CSS” implementation:


If you turn on an outline on one of the content sections it becomes clear how it exactly works:

  • The content sections also appear underneath the navigation
  • The content sections have background set, which has the exact same dimensions as their linked navigation item.
  • The backgrounds are positioned visually underneath their linked navigation item using background-position: fixed;

It’s a delicate combination, but I’m pretty excited that this actually works 🙂

Do note though:

  • You’ll need to add an extra wrapper whose width is limited around the section’s content, to prevent the content from appearing underneath the navigation.
  • Active state is a bit tricky:

    • Items high up the navigation list — such as Authentication — will become active pretty late, whereas items lower in the navigation bar — such as Expanders — will get their active state applied quite early. This is because the active state of nav items “starts” when the top of its linked container intersects with the nav item itself.
    • For small content-sections — such as Links – the nav item will become inactive too early, even before its linked section has left the viewport (and even when it still entirely in view). This is because the content itself is not that big and the nav item is pretty low in the navigation list.
  • You’re limited to background effects only. No making the nav items bold/italic, or changing their color.

Building a Settings Component

In this episode of GUI Challenges, Adam Argyle builds a Settings Component with sliders and checkboxes.

In this post I want to share thinking on building a Settings component for the web that is responsive, supports multiple device inputs, and works across browsers.

Tons of CSS-knowledge in there:

Building a Settings Component (Demo) →Building a Settings Component (Writeup) →

Building Future UIs

The folks over at Formidable have been experimenting with Houdini and WebGL/Three.js to create futuristic UIs

Futuristic sci-fi UIs in movies often support a story where humans, computers, and interfaces are far more advanced than today, often mixed with things like super powers, warp drives, and holograms. What is it about these UIs that feel so futuristic and appealing? Can we build some of these with the web technologies we have today?

Building Future UIs →

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.

React Native Bottom Sheet (Slide Up Panel)

A common pattern in Mobile Apps is to have a Bottom Sheet / Slide Up Panel. React Native Slack Bottom Sheet is a wrapper around Slack’s native PanModal component for iOS.

import SlackBottomSheet from 'react-native-slack-bottom-sheet';

 <View style={StyleSheet.absoluteFillObject}>
     <Lorem />

React Native Slack Bottom Sheet →
PanModal →

💁‍♂️ If you’re looking for a slide up panel that also works on Android check out rn-sliding-up-panel, a pure JS implementation.

Note that it will require some tweaking before you get a somewhat similar result like its native counterpart. — I remember it took me quite some time to get everything right while working on the EV-Point App.

😅 Still an Ionic 1 Developer? I once created a similar component for it. It didn’t feel natural at all because the gestures weren’t implemented properly (because: Deadlines™). Eventually we implemented in the project I was then working on, but without the ability to manually drag it.

Prevent content from being hidden underneath a fixed header by using scroll-margin-top

If you’ve ever implemented a design with a fixed header, you’ve surely had this problem:

You click a jump link like <a href="#header-3">Jump</a> which links to something like <h3 id="header-3">Header</h3>. That's totally fine, until you have a position: fixed; header at the top of the page obscuring the h3 you're trying to link to!

Fixed headers have a nasty habit of hiding the element you’re trying to link to.

Thankfully Chris Coyier from CSS-Tricks found and shared the straightforward solution:

h3 {
  scroll-margin-top: 5rem; /* whatever is a nice number that gets you past the header */

🐛 As noted in the comments below this doesn’t work Safari. In that browser you’ll need to use scroll-snap-margin-top. All other modern browsers do have excellent support for scroll-margin-top and play nice.


To not have to apply the CSS rule to too many elements, I’d adjust the snippet to use the :target selector.

The :target CSS pseudo-class represents a unique element (the target element) with an id matching the URL’s fragment.

That way it will work with any internally linked thing (headers in all their sizes, anchors, etc):

:target {
  scroll-margin-top: 5rem;

It was also nice to see that Mattias Geniar used this solution when implementing the Smooth Scrolling Sticky Navigation I wrote about earlier (Mattias is using scroll-padding-top though).

Fixed Headers and Jump Links? The solution is scroll-margin-top


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.

Craft.js – A React framework for building drag-n-drop page editors.

Page editors are a great way to provide an excellent user experience. However, to build one is often a pretty dreadful task.

Craft.js solves this problem by modularising the building blocks of a page editor. It provides a drag-n-drop system and handles the way user components should be rendered, updated and moved – among other things. With this, you’ll be able to focus on building the page editor according to your own specifications and needs.

Craft.js →

💡 If you’re looking for an editor that’s more focused on content (instead of the entire page layout), check out editor.js

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

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 ='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.

Fundamentals of Hierarchy in Interface Design

Visual hierarchy is the order in which the user process information by importance. In interface design, like in any other form of design, this concept is necessary to be functional at sight. With the correct use of hierarchy, the mind can group and prioritize elements to give them a specific order, which facilitates the understanding of what you want to communicate and the sense of achievement by the user.

Talks about seven resources to take into account to create a correct hierarchy:

  1. Size: The larger the element, the more it will attract attention.
  2. Color: Bright colors stand out more than muted tones.
  3. Proximity: Elements close to each other attract more attention than distant elements.
  4. Alignment: Any element that separates from the alignment of the others will attract attention.
  5. Repetition: Repeated styles give the impression that the elements are related.
  6. Negative Space: The more space around the element, the more attention it generates.
  7. Texture: Varied and complex textures attract more attention than flat ones.

Fundamentals of Hierarchy in Interface Design →

💵 This article is stuck behind Medium’s metered paywall … open the link in an incognito window to bypass that limit.

How to Design Delightful Dark Mode Themes

When done well, dark themes have many benefits. They reduce eyestrain. They are easier to read in low light. And, depending on the screen, they can greatly reduce battery consumption.

However, it is difficult to create a delightful dark theme. We cannot simply reuse our colors or invert our shades. If we do, we will achieve the opposite of what we want: we will increase eyestrain and make it harder to read in low light. We may even break our information hierarchy.

The stuff mentioned should help you to create readable, balanced, and delightful “Dark Mode” themes.

How to design delightful dark themes →

🔥 If you’re wondering how to easily implement these in your JavaScript application, I’ve done an extensive writeup on the subject. All major frameworks (React, Vue, Angular, etc.) are covered!