fluid-mechanics-and-dynamics
Implementing Custom Scrollbars with Javascript and Css for Better Ui
Table of Contents
Why Custom Scrollbars?
Default scrollbars differ drastically across browsers and operating systems. Chrome, Firefox, Safari, Edge, and legacy Internet Explorer each render scrollbars with distinct widths, colors, and behaviors. This inconsistency can break a carefully designed UI, making a polished site appear unfinished or fragmented. Custom scrollbars solve this by giving you full control over appearance, ensuring a cohesive brand experience on every platform.
Beyond aesthetics, custom scrollbars can improve usability. You can add visual cues like gradient thumbs, subtle hover effects, or animated progress bars that inform users of their current scroll position. When implemented thoughtfully, they also support better accessibility by respecting user preferences for reduced motion or increased contrast.
However, custom scrollbars come with trade-offs. They can be more complex to build, may affect performance if not optimized, and risk harming accessibility if they shrink or hide scroll indicators. This article will walk you through professional techniques—starting with pure CSS solutions, then layering JavaScript for interactive enhancements—to produce scrollbars that look great, work everywhere, and remain user-friendly.
CSS-Only Custom Scrollbars
Modern CSS provides several properties and pseudo-elements for styling native scrollbars. The approach differs between WebKit-based browsers (Chrome, Safari, Edge) and Firefox. Internet Explorer and older Edge Legacy are not supported with CSS scrollbar styling; those versions fall back to default scrollbars.
WebKit Pseudo-Elements
Browsers using the WebKit engine (including Blink-based Chromium) support the ::-webkit-scrollbar family of pseudo-elements. You can style the track, thumb, and optionally the buttons (arrows) and corner pieces. The most common pattern sets the thumb and track colors and gives the thumb rounded corners:
/* Sets scrollbar width */
::-webkit-scrollbar {
width: 10px;
height: 10px;
}
/* Track background */
::-webkit-scrollbar-track {
background: #f4f4f4;
border-radius: 10px;
}
/* Thumb (draggable part) */
::-webkit-scrollbar-thumb {
background: #2c3e50;
border-radius: 10px;
border: 2px solid transparent;
background-clip: padding-box;
}
/* Thumb on hover */
::-webkit-scrollbar-thumb:hover {
background: #34495e;
}
/* Optional: corner where horizontal and vertical scrollbars meet */
::-webkit-scrollbar-corner {
background: transparent;
}
The background-clip: padding-box trick creates a small transparent gap around the thumb, preventing it from touching the track edges. This technique is widely used in modern UIs. You can also style scrollbar buttons (::-webkit-scrollbar-button) but they are rarely needed and can look clunky.
Firefox Scrollbar Properties
Firefox supports the scrollbar-width and scrollbar-color properties, which control the thickness and colors of the scrollbar. These properties apply to the overflow element and affect both the track and thumb. Firefox does not support pseudo-selectors for individual parts, but the two properties provide sufficient control for most designs:
/* On the element that has overflow */
.scrollable-content {
scrollbar-width: thin; /* auto | thin | none */
scrollbar-color: #2c3e50 #f4f4f4; /* thumb color, track color */
}
The scrollbar-width property accepts auto (browser default thickness), thin (narrower variant), or none (hides the scrollbar entirely—use with caution for accessibility). scrollbar-color takes two color values: first for the thumb, second for the track. On macOS, setting scrollbar-width: thin may produce a very narrow scrollbar that is hard to click; test thoroughly across devices.
Combining WebKit and Firefox Styles
To provide custom scrollbars in both WebKit and Firefox, write both sets of rules. For Chrome, Safari, and Edge, the pseudo-elements will apply. For Firefox, the scrollbar-color and scrollbar-width apply. Since these are non-overlapping, you can safely include both in your stylesheet:
/* Firefox */
.scrollable {
scrollbar-width: thin;
scrollbar-color: #2c3e50 #f4f4f4;
}
/* WebKit */
.scrollable::-webkit-scrollbar {
width: 8px;
}
.scrollable::-webkit-scrollbar-track {
background: #f4f4f4;
}
.scrollable::-webkit-scrollbar-thumb {
background: #2c3e50;
border-radius: 4px;
}
If you need an identical visual across all modern browsers, you may need to use a JavaScript-based solution (covered later). But for many projects, the combination above delivers a consistent enough experience, with Firefox showing a slightly different thumb shape (rectangular instead of rounded) but using the same colors.
JavaScript-Enhanced Custom Scrollbars
CSS-only custom scrollbars are limited to color, width, and rounding. They cannot animate or respond to scroll velocity, display progress, or integrate with other UI components. JavaScript adds dynamic behavior: you can monitor scroll position, modify the scrollbar appearance in real time, or even hide the scrollbar when not in use.
Scroll Position Indicator (Progress Bar)
A common enhancement is a fixed progress bar that fills horizontally as the user scrolls down the page. This gives immediate feedback on reading progress. The JavaScript listens to the scroll event and calculates the percentage:
const progressBar = document.getElementById('scrolling-progress');
window.addEventListener('scroll', () => {
const scrollTop = window.scrollY;
const docHeight = document.documentElement.scrollHeight - window.innerHeight;
const scrollPercent = (scrollTop / docHeight) * 100;
progressBar.style.width = scrollPercent + '%';
});
The progress bar element is typically positioned fixed at the top of the viewport with a small height and a transition for smooth updates. To avoid performance issues from rapid scroll events, consider throttling or using requestAnimationFrame:
let ticking = false;
window.addEventListener('scroll', () => {
if (!ticking) {
window.requestAnimationFrame(() => {
// update progress bar
ticking = false;
});
ticking = true;
}
});
Hover and Fade Effects on the Thumb
Native scrollbar thumbs in many browsers only change appearance when hovered. With JavaScript, you can add smoother transitions, show a glowing border, or change the thumb color based on scroll position. You can even create a "ghost" scrollbar that fades out after a few seconds of inactivity:
let timeoutId;
const scrollbarThumb = document.getElementById('custom-thumb');
function showThumb() {
scrollbarThumb.classList.add('visible');
clearTimeout(timeoutId);
timeoutId = setTimeout(() => scrollbarThumb.classList.remove('visible'), 2000);
}
window.addEventListener('scroll', showThumb);
// Also show on mouseover of scrollable area
This technique requires you to build a custom scrollbar overlay (see next section) because the native thumb cannot be directly manipulated via JavaScript—you can only read scroll properties. Overlaying a custom scrollbar gives you full stylistic freedom but demands careful reimplementation of scroll behavior and touch support.
Building a Fully Custom Scrollbar with JavaScript
When CSS-native styling is insufficient, developers create entirely custom scrollbars using JavaScript and absolute positioning. This approach replaces the native scrollbar with a div that acts as a track and a draggable thumb. The underlying content still scrolls natively, but the custom scrollbar is positioned on top, hiding the default one.
Basic Structure
The scrollable container has overflow: hidden (or overflow: auto with the native scrollbar hidden via negative margins or scrollbar-width: none). Inside, a wrapper handles the actual scrolling. A separate div for the track and thumb sits absolutely positioned over the container:
<div class="scroll-container">
<div class="scroll-content">
<!-- long content -->
</div>
<div class="scrollbar-track">
<div class="scrollbar-thumb"></div>
</div>
</div>
Syncing Thumb Position with Scroll
The thumb's height corresponds to the visible portion of content. As the user scrolls, the thumb moves proportionally. Similarly, dragging the thumb updates the content scroll position. This synchronization is the core of any custom scrollbar implementation:
const container = document.querySelector('.scroll-container');
const content = document.querySelector('.scroll-content');
const track = document.querySelector('.scrollbar-track');
const thumb = document.querySelector('.scrollbar-thumb');
function updateThumb() {
const contentHeight = content.scrollHeight;
const containerHeight = container.clientHeight;
const scrollRatio = containerHeight / contentHeight;
const thumbHeight = Math.max(containerHeight * scrollRatio, 30);
const thumbTop = (container.scrollTop / (contentHeight - containerHeight)) * (containerHeight - thumbHeight);
thumb.style.height = thumbHeight + 'px';
thumb.style.top = thumbTop + 'px';
}
container.addEventListener('scroll', updateThumb);
window.addEventListener('resize', updateThumb);
updateThumb();
Dragging the thumb requires mouse event listeners on the thumb and document. When the user clicks the thumb, you capture the mouse position and calculate the new scroll position based on the thumb's movement. You must also handle mouseup and mousemove events precisely to avoid glitches. This logic is well-documented but can be tricky to get right for scrolling inertia, touch events, and nested scrollable elements.
Performance and Smoothness
Custom scrollbars that overlay native scrolling can cause layout reflows if not optimized. Always use transform for positioning the thumb instead of top to avoid triggering layout. Also, use will-change: transform on the thumb to promote it to its own compositor layer. Be mindful of the scroll event firing frequency—throttle or debounce where possible. Test on low-end devices to ensure the scrolling does not stutter.
Accessibility and Usability Considerations
Custom scrollbars often create accessibility problems. The most critical rule: never remove the ability to scroll with the keyboard or assistive technology. The native scrollbar handles keyboard focus, arrow keys, Page Up/Down, and screen reader announcements. When you hide or replace the native scrollbar, you must replicate all those behaviors manually.
Keyboard Navigation
Ensure the scrollable container remains focusable (add tabindex="0" if it is not an interactive element). Listen for keydown events and scroll accordingly. For example, Arrow Up/Down should scroll by line height, Page Up/Down by viewport height, and Home/End to top/bottom. Evaluate whether a fully custom scrollbar is worth the effort if keyboard navigation is critical—often a styled native scrollbar suffices.
Reduced Motion and Contrast
Respect the user's prefers-reduced-motion media query. If custom scrollbars use animations (e.g., fading thumb, spring physics), disable those animations when the user has requested reduced motion. Additionally, ensure sufficient color contrast between the thumb and track. The Web Content Accessibility Guidelines (WCAG) recommend a contrast ratio of at least 3:1 for non-text elements. Use scrollbar-color with high-contrast pairs.
Testing on Mobile and Touch Devices
Many users on mobile browsers never see scrollbars (they appear only while scrolling and disappear quickly). Custom scrollbars that are fixed and always visible can clutter a small screen. Consider hiding custom scrollbars on touch devices and relying on overscroll indicators. Use feature detection or media queries (hover capability) to decide whether to show the custom scrollbar.
Cross-Browser Compatibility and Fallbacks
No single CSS method works in all browsers. Internet Explorer and old Edge lack scrollbar styling entirely. For these browsers, you have two options: accept the default look, or use a JavaScript polyfill that reimplements the scrollbar completely.
Progressive Enhancement with CSS
Start with the native scrollbar. Apply scrollbar-color and scrollbar-width for Firefox, and ::-webkit-scrollbar for WebKit browsers. The default scrollbar in unsupported browsers will remain unchanged—this is perfectly acceptable. Most users will not notice missing styling if the rest of the UI is consistent.
JavaScript Libraries
Several lightweight libraries exist for custom scrollbars that work across older browsers and provide better accessibility than a home-rolled solution. Examples include SimpleBar, OverlayScrollbars, and Perfect Scrollbar. These libraries handle mouse, touch, keyboard, and resizing events. They also manage ARIA attributes and provide fallbacks for browsers without native support. Before building from scratch, evaluate whether a library meets your needs with less effort and lower maintenance.
When to Avoid Custom Scrollbars
Custom scrollbars are not appropriate for every project. If your audience includes users with disabilities, or if you need to support legacy browsers (IE11, old Safari), sticking with native scrollbars is safer. Also, if your design relies on system-level scroll behavior (like overscroll bounce on macOS), custom implementations can feel jarring. Reserve custom scrollbars for applications where visual consistency is paramount and you have dedicated time for quality assurance and accessibility testing.
Conclusion
Implementing custom scrollbars with CSS and JavaScript can give your UI a polished, unified look. Start with the easiest path: use CSS pseudo-elements for WebKit browsers and scrollbar-color/scrollbar-width for Firefox. This covers most modern browsers with minimal code. If you need interactive features like scroll progress indicators or animated thumbs, layer JavaScript on top of the native scrollbar—avoid fully replacing the scrollbar unless absolutely necessary.
For advanced horizontal consistency across all browsers, consider a well-maintained JavaScript library that handles the heavy lifting of cross-platform compatibility and accessibility. Always test custom scrollbars with keyboard navigation and screen readers, and respect reduced-motion settings. When done right, a custom scrollbar becomes an invisible part of your interface that feels natural and never hinders the user.
Further reading: MDN: ::-webkit-scrollbar, MDN: scrollbar-color, and CSS-Tricks: Performance Considerations for Custom Scrollbars.