Scroll Fade
Mask-based fade hints on the edges of a scroll container — pure CSS, scroll-position aware, and RTL-safe.
Last updated
scroll-fade is a Tailwind @utility that fades the edges of a scroll container
to hint at more content. It uses mask-image with a scroll-driven timeline — no
JavaScript and no scroll listeners. The fade is crisp at the start/end and softens
mid-scroll; without scroll-driven-animation support it degrades to a static fade.
Install
These utilities ship with @vegastack/design-tokens — no shadcn add step:
@import "@vegastack/design-tokens/utilities.css";The @vegastack/design imports it for you, so it is available
automatically alongside shimmer.
Usage
<div className="scroll-fade h-72 overflow-y-auto">{/* content */}</div>The container needs a bounded size and overflow so it can actually scroll.
Examples
Horizontal
scroll-fade-x fades the start/end edges of a horizontally-scrolling row.
Single edge
Fade only one edge with scroll-fade-t or scroll-fade-b (and -l/-r or the
logical -s/-e).
Fixed size
scroll-fade-<n> sets a fixed fade depth from the spacing scale.
Classes
| Class | Effect |
|---|---|
scroll-fade / scroll-fade-y | Vertical fade (top + bottom). |
scroll-fade-x | Horizontal fade (start + end). |
scroll-fade-t / scroll-fade-b | Top / bottom edge only. |
scroll-fade-l / scroll-fade-r | Left / right edge only. |
scroll-fade-s / scroll-fade-e | Logical start / end edge (RTL-aware). |
scroll-fade-* | Fixed fade depth for all active edges (spacing scale or a length/percentage). |
scroll-fade-t-* | Fixed fade depth for the top edge only (spacing scale or a length/percentage). |
scroll-fade-b-* | Fixed fade depth for the bottom edge only (spacing scale or a length/percentage). |
scroll-fade-s-* | Fixed fade depth for the logical start edge only, RTL-aware (spacing scale or a length/percentage). |
scroll-fade-e-* | Fixed fade depth for the logical end edge only, RTL-aware (spacing scale or a length/percentage). |
scroll-fade-none | Disable the fade. |
Notes
- Relies on CSS scroll-driven animations (
animation-timeline: scroll()); in engines without support it falls back to a static bilateral fade. - It is the edge treatment used by the Message Scroller viewport.
- It clips the element's own focus outline.
mask-imagelimits everything the element paints to its border box, so on a keyboard-focusable scroll container the shared:focus-visibleoutline — drawn at a positive offset, outside that box — is not painted at all. Pull it inside withfocus-visible:-outline-offset-2. Do not substitute a border tint:forced-colors: activereplacesborder-color, which would leave the container with no focus indicator whatsoever. See Focus ring.