Skip to main content

    Lesson 46 • Advanced Track

    Custom Scrollbars & UI Chrome Styling

    By the end you'll style any scrollbar to match your theme — in every browser — without hurting usability.

    What You'll Learn

    • Style scrollbars the standard way with scrollbar-width and scrollbar-color
    • Use the WebKit ::-webkit-scrollbar / -thumb / -track pseudo-elements for fine control
    • Animate scrolling with scroll-behavior: smooth and snap cards with scroll-snap
    • Trap scrolling inside a panel with overscroll-behavior: contain
    • Write one cross-browser snippet that styles scrollbars everywhere
    • Keep scrollbars usable and accessible — never invisible or too thin to click
    Prerequisites: You should be comfortable with CSS selectors and the box model from CSS Basics & Selectors. A scrollbar only appears when content overflows a fixed-size box, so it helps to know how height and overflow work together.

    💡 Real-World Analogy

    Think of a scrollbar like the volume slider on a hi-fi. The track is the groove it slides in, and the thumb is the knob you grab. Default scrollbars are the plain plastic knob the factory fitted; a custom scrollbar is the same mechanism with a knob that matches your gear. You're repainting the knob — not changing how it works — so keep it big enough to grab and easy to see.

    The Two Scrollbar APIs

    A scrollbar has two parts you can style: the thumb (the bit you drag) and the track (the groove behind it). There are two ways to style them, and you need both.

    The standard properties. scrollbar-width takes auto, thin, or none, and scrollbar-color takes two colours — thumb first, then track. These work in Firefox and modern Chrome (121+). They're simple but you only get colour and a coarse width.

    The WebKit pseudo-elements. ::-webkit-scrollbar targets the whole bar, ::-webkit-scrollbar-thumb the handle, and ::-webkit-scrollbar-track the groove. They work in Chrome, Safari and Edge and give you exact pixel widths, border-radius, borders, gradients and :hover states — but Firefox ignores them completely.

    The takeaway: write both. The standard properties cover Firefox; the WebKit pseudo-elements add the polish for everyone else.

    📊 WebKit Scrollbar Pseudo-Elements

    Pseudo-ElementWhat It StylesCommon Properties
    ::-webkit-scrollbarThe entire scrollbarwidth, height
    ::-webkit-scrollbar-thumbThe draggable handlebackground, border-radius, border
    ::-webkit-scrollbar-trackThe groove behind the thumbbackground, border-radius
    ::-webkit-scrollbar-thumb:hoverThe handle on hoverbackground (darker shade)

    1. Both APIs Together (Worked Example)

    Read every comment, then run it. The same box gets a styled scrollbar in both Firefox and Chrome because it declares the standard properties and the WebKit pseudo-elements together.

    One scrollbar, two APIs

    Try it Yourself »
    Code Preview
    <!DOCTYPE html>
    <html>
    <head>
    <style>
      body { font-family: system-ui, sans-serif; padding: 24px; color: #333; }
      h1 { color: #1565C0; }
    
      /* A box that is taller than its content area, so it must scroll. */
      .scroll-box {
        height: 180px;          /* fixed height ... */
        overflow-y: auto;       /* ... + overflow:auto = a vertical scrollbar */
        border: 1px solid #ddd;
        border-radius: 8px;
        padding: 16px;
      }
    
      /* --- API 1: the STANDARD properties (Firefox + modern Chrome) --- 
    ...

    2. Smooth Scrolling & Scroll-Snap

    scroll-behavior: smooth animates jumps (handy for anchor links). scroll-snap-type makes a scroll container snap each child into place — the classic carousel feel. Note the horizontal bar uses height, not width.

    3. Containing the Scroll with overscroll-behavior

    When you scroll to the end of a panel, the page behind it normally takes over — that's "scroll chaining". overscroll-behavior: contain stops it, which is exactly what you want for chat windows and modals.

    overscroll-behavior: contain

    Try it Yourself »
    Code Preview
    <!DOCTYPE html>
    <html>
    <head>
    <style>
      body { font-family: system-ui, sans-serif; padding: 24px; color: #333; height: 1200px; }
      h1 { color: #1565C0; }
    
      .panel {
        height: 160px;
        overflow-y: auto;
        border: 1px solid #ddd; border-radius: 8px; padding: 16px;
        scrollbar-width: thin;
        scrollbar-color: #4CAF50 #E8F5E9;
    
        /* overscroll-behavior: contain stops the "scroll chaining" effect — when
           you reach the bottom of this panel, the PAGE behind it does NOT start
           sc
    ...

    🎯 Your Turn #1 — Standard Properties

    Fill in the blanks so the box gets a thin scrollbar with a purple thumb on a light track. This is the Firefox / standards approach — only two lines.

    Fill in scrollbar-width & scrollbar-color

    Try it Yourself »
    Code Preview
    <!DOCTYPE html>
    <html>
    <head>
    <style>
      body { font-family: system-ui, sans-serif; padding: 24px; color: #333; }
    
      .box {
        height: 160px;
        overflow-y: auto;            /* makes it scrollable */
        border: 1px solid #ddd; border-radius: 8px; padding: 16px;
    
        /* 🎯 YOUR TURN — fill in the blanks marked with ___ */
    
        /* 1) Make the scrollbar thin (auto | thin | none) */
        scrollbar-width: ___;        /* 👉 replace ___ with the keyword for slim */
    
        /* 2) Set the thumb to purple (
    ...

    🎯 Your Turn #2 — WebKit Pseudo-Elements

    Now do the same job the WebKit way. Decide whether each blank is width/height, thumb/track, and which :state gives hover feedback.

    Finish the WebKit scrollbar

    Try it Yourself »
    Code Preview
    <!DOCTYPE html>
    <html>
    <head>
    <style>
      body { font-family: system-ui, sans-serif; padding: 24px; color: #333; }
      .box { height: 160px; overflow-y: auto; border: 1px solid #ddd;
             border-radius: 8px; padding: 16px; }
    
      /* 🎯 YOUR TURN — finish the WebKit scrollbar (Chrome/Safari/Edge) */
    
      /* 1) Make the whole scrollbar 12px wide */
      .box::-webkit-scrollbar { ___: 12px; }          /* 👉 width or height? */
    
      /* 2) Give the draggable handle a teal background + rounded corners */
      .bo
    ...

    When to Use Custom Scrollbars

    • Dark-themed apps: default grey bars look jarring on dark backgrounds — match the thumb to your theme.
    • Chat & messaging: a thin 4–6px bar feels more app-like; pair it with overscroll-behavior: contain.
    • Code editors: a subtle dark-on-dark bar keeps the focus on the code.
    • Carousels: style (or hide on touch) the horizontal bar and add scroll-snap-type for polish.

    🏆 Mini-Challenge — Dark-Mode Chat Scrollbar

    Support is faded now — you get only a comment outline. Build a subtle dark-mode scrollbar on .chat using both APIs, plus overscroll-behavior: contain. Check your work against the expected result in the comment.

    Mini-Challenge: style .chat

    Try it Yourself »
    Code Preview
    <!DOCTYPE html>
    <html>
    <head>
    <style>
      body { font-family: system-ui, sans-serif; padding: 24px; color: #ccc;
             background: #1a1a2e; }
      h2 { color: #90CAF9; }
    
      /* 🎯 MINI-CHALLENGE: a dark-mode chat panel scrollbar
         1. Make ".chat" 200px tall and scrollable (height + overflow-y).
         2. Add overscroll-behavior: contain so the page doesn't scroll behind it.
         3. STANDARD API: scrollbar-width thin; scrollbar-color = #555 thumb on #1a1a2e track.
         4. WEBKIT API: 8px wide bar; 
    ...

    Common Errors & Fixes

    • "My scrollbar styles work in Chrome but not Firefox." You only wrote ::-webkit-scrollbar rules, which Firefox ignores. Fix: add scrollbar-width and scrollbar-color to the same selector.
    • "No scrollbar appears at all." The element isn't actually overflowing. Fix: give it a fixed height (or max-height) and overflow-y: auto — a scrollbar only shows when content is taller than the box.
    • "My horizontal bar is invisible." You set width on ::-webkit-scrollbar. A horizontal bar's thickness is its height. Fix: use ::-webkit-scrollbar { height: 8px; } and overflow-x: auto on the container.
    • "Users say they can't grab the scrollbar." You made it too thin (1–3px). Fix: keep desktop bars at least 6–8px wide so they're clickable; thin is fine, hair-thin is not.
    • "People keep missing that the box scrolls." You hid the bar with scrollbar-width: none on vertical content. Fix: only hide scrollbars where swipe is obvious (touch carousels); otherwise keep a visible cue.

    📋 Quick Reference

    Property / SelectorDoesExample
    scrollbar-widthStandard bar thicknessthin
    scrollbar-colorThumb then track colour#1976D2 #f0f0f0
    ::-webkit-scrollbarWhole bar (width/height){ width: 8px; }
    ::-webkit-scrollbar-thumbDraggable handle{ background: #1976D2; }
    ::-webkit-scrollbar-trackGroove behind thumb{ background: #f0f0f0; }
    scroll-behaviorAnimate scroll jumpssmooth
    scroll-snap-typeSnap children into placex mandatory
    overscroll-behaviorStop scroll chainingcontain

    💡 Pro tip: always pair the standard properties with the WebKit pseudo-elements so every browser gets a styled bar.

    Frequently Asked Questions

    Why isn't my custom scrollbar showing in Firefox?

    Firefox ignores the ::-webkit-scrollbar pseudo-elements — they are WebKit-only. Firefox styles scrollbars with the standard scrollbar-width and scrollbar-color properties instead. Always write both APIs: the standard properties for Firefox and modern Chrome, and the ::-webkit-scrollbar pseudo-elements for Chrome, Safari and Edge.

    Should I style horizontal scrollbars differently?

    The pseudo-elements are the same, but you set height instead of width on ::-webkit-scrollbar (height controls the thickness of a horizontal bar). The container also needs overflow-x: auto instead of overflow-y. Everything else — thumb, track, hover — works identically.

    Is it OK to completely hide the scrollbar?

    Only when scrolling is obvious another way — for example a horizontal card carousel on a touch device where users swipe. Hiding the scrollbar on ordinary vertical content (scrollbar-width: none) is an accessibility problem because people lose the visual cue that there is more to read. When in doubt, keep a visible bar.

    What does scroll-behavior: smooth do?

    It animates programmatic and anchor-link scrolling instead of jumping instantly. Set it on the html element (or a scroll container) and clicking an in-page #anchor link, or calling element.scrollIntoView(), glides smoothly. It has no effect on the scrollbar's appearance — it only changes how the scroll motion looks.

    When should I use overscroll-behavior?

    Use overscroll-behavior: contain on scrollable panels like chat windows, dropdowns and modals. It stops 'scroll chaining' — reaching the end of the inner panel no longer scrolls the page behind it. This keeps the user's focus inside the panel and prevents the surprising jump where the whole page starts moving.

    🎉 Lesson Complete

    • ✅ Standard API: scrollbar-width + scrollbar-color (thumb then track)
    • ✅ WebKit API: ::-webkit-scrollbar / -thumb / -track + :hover
    • ✅ Always write both APIs for full cross-browser coverage
    • ✅ Horizontal bars use height; add scroll-snap-type for carousels
    • scroll-behavior: smooth animates jumps; overscroll-behavior: contain stops chaining
    • ✅ Keep bars visible and at least ~6px wide — never invisible or too thin to grab
    • ✅ Next lesson: Accessible Modals & Dialogs

    Sign up for free to track which lessons you've completed and get learning reminders.

    Previous

    Cookie & Privacy Settings

    We use cookies to improve your experience, analyze traffic, and show personalized ads. You can manage your preferences below.

    By clicking "Accept All", you consent to our use of cookies for analytics and personalized advertising. You can customize your preferences or reject non-essential cookies.

    Privacy PolicyTerms of Service