
Styling Tauri Scrollbars with OverlayScrollbars
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.
