A scroll container whose edges fade only where there is more to scroll, animated as you scroll.
Changelog
16 releases- 2.4.0Scroll fadeNew
- 2.3.2Tabs keyboard navigationSep 28
- 2.3.1Highlight follows scrollingSep 21
- 2.3.0Base UI drawerNew
- 2.2.4Faster tooltip warm-upSep 7
- 2.2.3Reduced motion everywhereAug 31
- 2.2.2React Aria selectAug 24
- 2.2.1Tempo providerAug 17
- 2.2.0AccordionNew
- 2.1.0Motion 14Aug 3
- 2.0.3Sliding number decimalsJul 27
- 2.0.2Dialog origin from triggerJul 20
- 2.0.1Smaller bundleJul 13
- 2.0.0Drop-in primitivesNew
- 1.9.4Icon loop delayJun 29
- 1.9.3Ripple on keyboard pressJun 22
Installation
Usage
import { ScrollFade } from '@animate-ui/react/primitives/effects/scroll-fade';
export function Changelog() {
return (
<ScrollFade className="h-64">
<ul>{/* … */}</ul>
</ScrollFade>
);
}orientation="horizontal" fades the left and right edges, "both" all four. With asChild, the child element becomes the scroll container.
To fade a scroll element you don't render yourself (a library's viewport), pass its ref to useScrollFade:
import { useRef } from 'react';
import { ScrollArea } from 'radix-ui';
import { useScrollFade } from '@animate-ui/react/primitives/effects/scroll-fade';
function Sidebar() {
const viewportRef = useRef<HTMLDivElement>(null);
useScrollFade(viewportRef, { size: 32 });
return <ScrollArea.Viewport ref={viewportRef}>{/* … */}</ScrollArea.Viewport>;
}Animation
- An edge fades only when there is content past it: none at the edge you're at, none at all when nothing overflows. The fade follows the scroll position directly, with no lag: over the first half of
sizescrolled away from an edge (20px by default), that edge goes from sharp to faded, and back as you return. The fade keeps its depth (size); only how much the edge clears changes. - On page load, the fade is already in place: nothing animates in.
- The items shrink and blur into the fade with it: from 1.75 times
sizeaway from a faded edge, each one eases toward 97% (shrink, 0.03) and a 4px blur (blur) as its center nears the edge, following the scroll the same way. Their places are measured when the content changes and once as a scroll starts, never every frame, and they're written once per frame. An item with its ownfilterkeeps it and isn't blurred. The items are the children of the first element holding several (a list's items, through itsul), and a group among them is opened up: aul,ol,role="list"orrole="group", or an element holding one (a section with its title and list), shrinks line by line, never as a whole. They're shrunk with thescaleproperty so their owntransformstays. - It's a
mask-image, so the content itself dissolves: it works on any background, in light and dark. - Scrolling never re-renders React: the amounts are motion values written to CSS variables (
--scroll-fade-top,--scroll-fade-bottom,--scroll-fade-left,--scroll-fade-right, from 0 to 1). - When the content changes size without a scroll (items added or removed), the edges ease to their new amount (200ms,
transition). - Under reduced motion, the fade and the blur follow the scroll position the same way, and jump when the content changes size; nothing shrinks.
API Reference
ScrollFade
| Prop | Type | Default |
|---|---|---|
asChild? | boolean | false |
orientation? | 'vertical' | 'horizontal' | 'both' | 'vertical' |
size? | number | 40 |
shrink? | number | 0.03 |
blur? | number | 4 |
transition? | Transition | { duration: 0.2, ease: [0.23, 1, 0.32, 1] } |
enabled? | boolean | true |
...props? | React.ComponentProps<'div'> | - |
useScrollFade
useScrollFade(ref, options) applies the fade to the element of ref. It takes the options of ScrollFade (orientation, size, shrink, blur, transition, enabled) and returns the motion values of each edge (top, bottom, left, right). It doesn't set the overflow: the element must already scroll.