Michael Charles Aubrey

https://MichaelCharl.es/Aubrey

Styling Tauri Scrollbars with OverlayScrollbars

Styling Tauri Scrollbars with OverlayScrollbars

Michael Charles Aubrey // Mon Aug 04 2025

I've been struggling to get custom scrollbars working in a Tauri app I'm building as a side project. In the past I've had success with simply using ::-webkit-scrollbar, but recently that doesn't seem to be working. User x4080 on GitHub suggested using the overlayscrollbars library, which worked great for me. However, the setup is a little less intuitive than I'd have liked, so I thought I'd throw together a quick guide on how to get it working.

For this, we'll be working on a Tauri app using React, which is styled using Tailwind CSS. However, I'm sure you could adapt this if necessary.

First, I installed overlayscrollbars

npm install overlayscrollbars

I then used import to include the overlayscrollbars styles in my css, as well as prepared styles for my custom scrollbars.

@import "overlayscrollbars/overlayscrollbars.css";

.os-theme-custom.os-scrollbar {
  --os-size: 8px;
  --os-padding-perpendicular: 0px;
  --os-padding-axis: 0px;
}

.os-theme-custom.os-scrollbar .os-scrollbar-track {
  background: rgba(120, 90, 200, 0.12);
  border-radius: 4px;
}

.os-theme-custom.os-scrollbar .os-scrollbar-handle {
  background: rgba(80, 60, 180, 0.45);
  border-radius: 4px;
  transition: background-color 0.2s ease;
}

.os-theme-custom.os-scrollbar .os-scrollbar-handle:hover {
  background: rgba(80, 60, 180, 0.7);
}

I then used this hook to initialize overlayscrollbars on scrollable elements. This works largely because I'm using Tailwind, so I could select for .overflow-y-auto and overflow-auto. If you aren't using tailwind you'll need a different approach.

import { OverlayScrollbars } from "overlayscrollbars";
import { useEffect } from "react";

export const useOverlayScrollbars = () => {
  useEffect(() => {
    // Initialize OverlayScrollbars on all scrollable elements
    const initScrollbars = () => {
      // Target the main scrollable areas
      const scrollableElements = [
        document.body,
        document.querySelector('[data-scrollable="true"]'),
        ...document.querySelectorAll(".overflow-y-auto"),
        ...document.querySelectorAll(".overflow-auto"),
      ].filter(Boolean) as Element[];

      scrollableElements.forEach((element) => {
        const htmlElement = element as HTMLElement;
        if (
          htmlElement &&
          !htmlElement.hasAttribute("data-overlayscrollbars-initialize")
        ) {
          OverlayScrollbars(htmlElement, {
            scrollbars: {
              theme: "os-theme-custom",
              autoHide: "never",
            },
          });
          htmlElement.setAttribute("data-overlayscrollbars-initialize", "true");
        }
      });
    };

    // Initialize immediately
    initScrollbars();

    // Re-initialize when DOM changes (for dynamic content)
    const observer = new MutationObserver(() => {
      setTimeout(initScrollbars, 100);
    });

    observer.observe(document.body, {
      childList: true,
      subtree: true,
      attributes: true,
      attributeFilter: ["class"],
    });

    return () => {
      observer.disconnect();
      // Clean up all OverlayScrollbars instances
      document
        .querySelectorAll("[data-overlayscrollbars-initialize]")
        .forEach((element) => {
          const instance = OverlayScrollbars(element as HTMLElement);
          if (instance) {
            instance.destroy();
          }
        });
    };
  }, []);
};

Then in App.tsx I called the hook.

import { useOverlayScrollbars } from "./hooks/useOverlayScrollbars";

function App() {
  // Initial state...

  // Initialize custom scrollbars
  useOverlayScrollbars();

  // Other hooks...

  return <main>{/* App content ... */}</main>;
}

export default App;

And that's it! So far this has worked nicely for styling scrollbars in Snappad, my side project.

Snappad