Skip to main content

Customizing Scrollbar Using CSS

Written by on .

engineering
css
macOS
Windows

  1. CSS
    1. Easing the Scrollbar Transition
      1. Determining When the Scrollbar Is Active
        1. Using CSS Variables
        2. Browser Support
          1. Conclusion

            A user reported a bug and attached a screenshot that looks like this:

            Screenshot showing a Windows style scrollbar
            Screenshot showing a Windows style scrollbar

            The bug is not related to the scrollbar, but I wasn't aware that that's how Glama looks on Windows.

            Screenshot showing a macOS style scrollbar
            Screenshot showing a macOS style scrollbar

            I wanted to customize the scrollbar to look like macOS, so I started to tinker with the CSS. To my surprise, this took quite a few iteration to get the desired result. This post summarizes the solution.

            CSS

            Here is the CSS that I used to make scrollbars on Windows look like macOS:

            @layer scroll { body.platform-windows { * { /* This prevent scrollbar from shifting the page content */ scrollbar-gutter: stable; &::-webkit-scrollbar { width: 8px; height: 8px; } &::-webkit-scrollbar-thumb { background: rgba(0, 0, 0, 0.8); border-radius: 8px; } &::-webkit-scrollbar-track { background: transparent; } &::-webkit-scrollbar-button { display: none; } } } }

            The body.platform-windows selector applies the styles to the body element when the platform-windows class is present. There is no need for us to override the default macOS scrollbar styles, so we add a platform-* class to the body element and use it to conditionally apply the styles.

            The result is identical to the macOS scrollbar. However, there is one small difference: macOS srollbar fades out when not in use, while Windows scrollbar stays visible. I wanted to replicate this on Windows.

            Easing the Scrollbar Transition

            If you are familiar with the scrollbar in macOS, it only shows when you are actively scrolling. After the user stops scrolling, the scrollbar fades out. This is how it looks on macOS:

            GIF showing the macOS scrollbar transition
            Scrollbar transition on macOS

            Let's see how we can achieve the same effect on Windows.

            Determining When the Scrollbar Is Active

            The first task is to determine when an element is being scrolled. We need this so that we could transition between showing and hiding the scrollbar.

            To do this, we use a React hook called useScrollbarThumb that returns a scrolling boolean and a setScrolling function. The scrolling boolean is true when the scrollbar is active, and the setScrolling function is used to set the scrolling boolean to true or false.

            import { useCallback, useEffect, useRef, useState } from 'react'; export const useScrollbarThumb = () => { const [scrolling, setScrolling] = useState(false); const timeoutRef = useRef<NodeJS.Timeout>(); const debouncedSetScrolling = useCallback(() => { if (timeoutRef.current) { clearTimeout(timeoutRef.current); } setScrolling(true); timeoutRef.current = setTimeout(() => { setScrolling(false); }, 1_000); }, []); useEffect(() => { return () => { if (timeoutRef.current) { clearTimeout(timeoutRef.current); } setScrolling(false); }; }, []); return { scrolling, setScrolling: debouncedSetScrolling, }; };

            Every element that needs to use the macOS-style scrollbar will need to need to have the data-scrollbar-thumb attribute. Here is how:

            const { scrolling, setScrolling } = useScrollbarThumb(); return ( <div data-scrollbar-thumb={scrolling} onScroll={() => { setScrolling(); }} > ... </div> );

            Now that we have data-scrollbar-thumb that is going to be either true or false, we can use it to conditionally apply the scrollbar styles.

            My first attempt was to just add transition: background-color 1s to the ::-webkit-scrollbar-thumb selector, but this didn't work. Turns out, we need a more clever solution.

            Using CSS Variables

            I was able to get the desired effect by using CSS variables. Here is the final CSS:

            @layer scroll { @property --scrollbar-thumb-color { syntax: "<color>"; inherits: true; initial-value: rgba(0, 0, 0, 0); } body.platform-windows { [data-scrollbar-thumb] { /* This prevent scrollbar from shifting the page content */ scrollbar-gutter: stable; transition: --scrollbar-thumb-color 1s; &::-webkit-scrollbar { width: 8px; height: 8px; } &::-webkit-scrollbar-thumb { background: var(--scrollbar-thumb-color); border-radius: 8px; } &::-webkit-scrollbar-track { background: transparent; } &::-webkit-scrollbar-button { display: none; } &[data-scrollbar-thumb="true"]:hover { --scrollbar-thumb-color: rgba(0, 0, 0, 0.8); } } } }

            Here is brief explanation of the key parts:

            • We are defining a CSS variable called --scrollbar-thumb-color that will be used to set the background color of the scrollbar thumb.
            • We are using [data-scrollbar-thumb="true"] to determine if the element is being scrolled.
            • We are using transition to smoothly transition the background color of the scrollbar thumb.

            Here is the final result:

            GIF showing the Windows scrollbar transition
            Scrollbar transition on Windows

            I am using BrowserStack to validate the results on Windows, and as far as I can tell, it looks like the scrollbar is working as expected.

            Browser Support

            It is worth mentioning that not all browsers support -webkit-scrollbar properties (namely, Firefox).

            Depeding on which browsers you are targeting, you may need to add a fallback, e.g.,

            @supports not selector(::-webkit-scrollbar) { scrollbar-color: rgba(0, 0, 0, 0.8) transparent; scrollbar-width: thin; }

            Conclusion

            That's it! This is how I customized Glama's scrollbars to provide a consistent experience across Windows and macOS.

            Whether customizing the default system scrollbar is the right choice depends on your specific needs. While consistency across platforms can enhance your application's visual identity, it's worth considering that users are familiar with their system's default scrollbar behavior. In our case, we prioritized visual consistency, but your mileage may vary depending on your project's requirements.