You Might Also Like
Scroll Direction Theme Shift — Free Direction-Aware Color Theme Effect
Scroll Direction Theme Shift · Scroll · Plain HTML, CSS & JS · Live preview
What's included
Features
About this UI Snippet
Scroll Direction Theme Shift — A Palette That Responds to Which Way You Scroll

Most "scroll-aware" theme effects only check one thing: has the user scrolled past some threshold, usually near the top of the page. This snippet checks something different and more genuinely direction-aware — which way the user is currently scrolling, determined fresh on every scroll event by comparing the current position against the previous one. Scroll down anywhere on the page and the accent colors shift to a cyan/indigo palette; scroll up from anywhere — not just near the top — and it shifts to a distinct pink/amber palette instead. Built entirely in vanilla JavaScript with CSS custom properties.
Comparing consecutive positions, not absolute position
The core of the effect is four lines: read window.scrollY, subtract the value recorded on the previous scroll event, and the sign of that difference is the direction. This is fundamentally different from checking scrollY > 100 or scrollY === 0 — those only know *where* you are on the page, not *which way* you're currently moving. A user paused halfway down the page could be about to scroll either direction; only comparing consecutive reads reveals which.
A deadband against jitter
Trackpads, certain mice, and even some scroll-restoration behaviors can fire scroll events with a pixel or two of noise even when the page is visually still. A small DEADBAND of 2px means deltas smaller than that are ignored entirely rather than treated as a direction change, so the theme doesn't flicker between states when nothing meaningful is happening.
CSS custom properties as the single source of truth
Rather than toggling many individual color declarations, the direction handler writes four CSS custom properties (--dt-accent, --dt-accent-2, --dt-bg, --dt-bg-2) on the root element, and every colored element in the CSS simply references those variables with a transition. Changing direction is then just four setProperty calls, and the whole page's palette cross-fades together via CSS transitions with no JavaScript animation loop needed.
Direction only changes state when it flips
The handler tracks the last known direction string and only calls applyTheme when the newly computed direction actually differs from it — so hundreds of consecutive "still scrolling down" events don't re-trigger the same transition repeatedly; only the moment of reversal does.
Customizing it
Add a third, more neutral theme for "scroll velocity near zero" using the deadband window, tie the shift to a background gradient position instead of flat colors, or apply it per-section instead of globally. Pair it with scroll color sections for position-driven color, or a dark mode toggle for manual theme control.
Build with AI
Build, Understand, Optimize, and Extend It With AI
Paste this snippet's JS into an AI coding assistant like Claude and ask it to explain why comparing window.scrollY against its previous value on every scroll event is a meaningfully different (and more useful) signal than checking whether scrollY exceeds a fixed threshold — the former tells you which way the user is currently moving, the latter only tells you where they are. It's also useful for extending the idea: ask it to add a third "idle" state that engages after a short debounce with no scroll events at all, distinct from the up/down states, or to make the deadband adapt to the device's reported scroll granularity so it behaves consistently across trackpads, mouse wheels, and touch.
Prompt to recreate it
Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:
Build a "scroll direction theme shift" effect in plain HTML, CSS, and vanilla JavaScript (no libraries).
Requirements:
- A page with several full-height content sections and a set of CSS custom properties defined on :root controlling an accent color, a secondary accent, and background colors, referenced throughout the CSS with transition declared on those properties for a smooth cross-fade.
- On scroll, determine direction by comparing the current window.scrollY against the value recorded on the previous scroll event (delta = currentY - lastY), NOT by checking whether scrollY exceeds a fixed threshold like "past 100px" or "near the top." The direction must be correctly detectable from any scroll position on the page, including reversing mid-page far from the top or bottom.
- Ignore deltas smaller than a small deadband (e.g. 2px) so minor scroll event jitter on an otherwise-still page does not cause spurious direction flips.
- Define two distinct theme objects (e.g. a "down" theme and an "up" theme) each with different values for the same set of CSS custom property names, and only call a function that writes those properties (via style.setProperty on the root element) when the newly computed direction actually differs from the previously stored direction — not on every scroll event.
- Add a small fixed-position indicator on screen showing the currently detected direction (e.g. an arrow and text label) so the effect is directly observable while testing.
- Confirm the theme flips correctly when scrolling down for a while, pausing, and then scrolling back up from the middle of the page — not just from the very top.Want to tighten it up first? Run this prompt through the AI Prompt Studio to score it across 8 quality dimensions, catch anti-patterns, and tune the wording for Claude, ChatGPT, or Gemini before you paste it in.
Step by step
How to Use
- 1Paste HTML, CSS, and JSAn intro, three content blocks, an indicator, and an outro render.
- 2Scroll downThe palette shifts to cyan/indigo and the indicator shows ↓.
- 3Scroll up from mid-pageThe palette flips to pink/amber immediately, not just near the top.
- 4Pause, then continueA small deadband prevents flicker while the page is still.
- 5Watch the indicatorIt shows the currently detected direction and arrow.
- 6Customize the themesEdit DOWN_THEME and UP_THEME's color values.
Real-world uses
Common Use Cases
Got questions?
Frequently Asked Questions
A "scrolled past top" effect only checks the absolute scroll position — usually a single threshold like scrollY > 100. This snippet instead subtracts the previous scroll position from the current one on every scroll event; the sign of that difference is the real, instantaneous scroll direction, which works correctly no matter where on the page the user currently is, including mid-scroll reversals far from the top.
Some input devices and browser scroll-restoration behaviors fire scroll events with a pixel or two of noise even when the page looks visually still. The DEADBAND constant ignores any delta smaller than 2px, so that noise doesn't get misread as a direction change and cause the theme to flicker when nothing meaningful is happening.
No — the handler computes the direction on every event but compares it against the last known direction, and only calls applyTheme (which writes the CSS custom properties and toggles the class) when the direction has actually flipped. Hundreds of consecutive "still scrolling down" events do nothing beyond updating the stored last position.
Writing four CSS custom properties on the root element means every element that references var(--dt-accent) and similar updates together automatically, and the built-in CSS transition on those properties handles the cross-fade with no JavaScript animation loop. It also makes customizing the palettes a one-line edit to the DOWN_THEME and UP_THEME objects rather than hunting through class definitions.
Attach the passive scroll listener in a mount effect, keeping lastY and direction in refs so they persist across renders without causing re-renders on every scroll event. Write the CSS custom properties via document.documentElement.style.setProperty exactly as in the vanilla version, since that state lives outside the component tree. Return a cleanup that removes the scroll listener.