Appearance
useScrollFade Hook API
React hook for detecting scroll position and determining when to show scroll fade indicators.
Import
tsx
import { useScrollFade, type UseScrollFadeOptions, type ScrollFadeState } from '@/shared/ui/components/scroll/useScrollFade'Signature
ts
function useScrollFade(
containerRef: RefObject<HTMLElement>,
options?: UseScrollFadeOptions
): ScrollFadeStateParameters
| Parameter | Type | Description |
|---|---|---|
containerRef | RefObject<HTMLElement> | Ref to the scrollable container |
options | UseScrollFadeOptions | Optional configuration |
Returns
ScrollFadeState
ts
interface ScrollFadeState {
/** Whether to show the top fade (content is scrolled down) */
showTop: boolean
/** Whether to show the bottom fade (more content below) */
showBottom: boolean
/** Whether the container is scrollable */
isScrollable: boolean
}Options
UseScrollFadeOptions
ts
interface UseScrollFadeOptions {
/** Threshold in pixels to trigger fade visibility (default: 10) */
threshold?: number
/** Debounce delay in ms for scroll events (default: 50) */
debounce?: number
/** Enable debug logging */
debug?: boolean
}Features
- Automatic detection: Monitors scroll position and container size
- Debounced: Prevents excessive updates (default: 50ms)
- Resize aware: Updates when container or content size changes
- Threshold support: Configurable trigger distance from edges
- Multi-library support: Works with SimpleBar, ScrollArea, and native overflow
- Performance optimized: Uses passive event listeners and ResizeObserver
How It Works
Finds the scrollable element:
- SimpleBar:
.simplebar-content-wrapper - Radix ScrollArea:
[data-radix-scroll-area-viewport] - Native: Container with
overflow
- SimpleBar:
Monitors scroll position:
- Listens to scroll events (debounced)
- Watches for resize changes
- Calculates if content is scrollable
Determines fade visibility:
showTop:scrollTop > thresholdshowBottom:scrollTop + clientHeight < scrollHeight - threshold
Examples
Basic Usage
tsx
import { useRef } from 'react'
import { useScrollFade } from '@/shared/ui/components/scroll/useScrollFade'
import { ScrollFade } from '@/shared/ui/components/scroll/ScrollFade'
function CustomScrollable() {
const containerRef = useRef<HTMLDivElement>(null)
const { showTop, showBottom } = useScrollFade(containerRef)
return (
<div ref={containerRef} className="relative">
<ScrollFade position="top" visible={showTop} />
<div className="h-[400px] overflow-y-auto">
{/* Scrollable content */}
</div>
<ScrollFade position="bottom" visible={showBottom} />
</div>
)
}With Custom Options
tsx
const { showTop, showBottom, isScrollable } = useScrollFade(containerRef, {
threshold: 20, // Show fade when 20px from edge
debounce: 150, // Debounce scroll events by 150ms
debug: true, // Enable console logging
})
// Only render fades if content is scrollable
return (
<div ref={containerRef} className="relative">
{isScrollable && (
<>
<ScrollFade position="top" visible={showTop} />
<ScrollFade position="bottom" visible={showBottom} />
</>
)}
{/* Content */}
</div>
)With SimpleBar
tsx
import SimpleBar from 'simplebar-react'
function ScrollableList() {
const containerRef = useRef<HTMLDivElement>(null)
const { showTop, showBottom } = useScrollFade(containerRef)
return (
<div ref={containerRef} className="relative">
<ScrollFade position="top" visible={showTop} />
<SimpleBar className="h-[500px]">
<div className="space-y-2 p-4">
{items.map((item) => (
<div key={item.id}>{item.name}</div>
))}
</div>
</SimpleBar>
<ScrollFade position="bottom" visible={showBottom} />
</div>
)
}With Radix ScrollArea
tsx
import { ScrollArea } from '@/shadcn/components/ui/scroll-area'
function ScrollableContent() {
const containerRef = useRef<HTMLDivElement>(null)
const { showTop, showBottom } = useScrollFade(containerRef)
return (
<div ref={containerRef} className="relative">
<ScrollFade position="top" visible={showTop} />
<ScrollArea className="h-[400px]">
{content}
</ScrollArea>
<ScrollFade position="bottom" visible={showBottom} />
</div>
)
}Conditional Rendering
tsx
const { showTop, showBottom, isScrollable } = useScrollFade(containerRef, {
threshold: 10,
})
// Show a message if content isn't scrollable
if (!isScrollable) {
return <div>All content is visible</div>
}
// Otherwise show with fades
return (
<div ref={containerRef} className="relative">
{showTop && <ScrollFade position="top" />}
{/* Content */}
{showBottom && <ScrollFade position="bottom" />}
</div>
)Implementation Details
Scroll Element Detection
The hook automatically detects the scrollable element:
ts
const getScrollElement = (): HTMLElement | null => {
// Try SimpleBar first
const simpleBarScroller = container.querySelector('.simplebar-content-wrapper')
if (simpleBarScroller) return simpleBarScroller
// Try ScrollArea (Radix)
const radixViewport = container.querySelector('[data-radix-scroll-area-viewport]')
if (radixViewport) return radixViewport
// Fallback to container if it has overflow
const hasOverflow = container.scrollHeight > container.clientHeight
return hasOverflow ? container : null
}Debouncing
Scroll events are debounced to improve performance:
ts
const debouncedCheck = () => {
if (debounceTimerRef.current) {
clearTimeout(debounceTimerRef.current)
}
debounceTimerRef.current = setTimeout(checkScroll, debounce)
}Resize Observation
The hook watches for size changes using ResizeObserver:
ts
const resizeObserver = new ResizeObserver(() => {
debouncedCheck()
})
resizeObserver.observe(scrollElement)Performance
Optimizations
- Passive event listeners: Improves scroll performance
- Debounced updates: Reduces re-renders (default: 50ms)
- ResizeObserver: Efficient size change detection
- Cleanup: Properly removes listeners and observers
Tips
- Increase
debouncefor less frequent updates - Use
thresholdto reduce unnecessary fade toggles - Consider
isScrollableto avoid rendering fades when not needed
Type Definitions
ts
interface UseScrollFadeOptions {
threshold?: number
debounce?: number
debug?: boolean
}
interface ScrollFadeState {
showTop: boolean
showBottom: boolean
isScrollable: boolean
}
function useScrollFade(
containerRef: RefObject<HTMLElement>,
options?: UseScrollFadeOptions
): ScrollFadeStateRelated
- ScrollFade API - Fade overlay component
- ScrollFadeContainer API - All-in-one wrapper
- ScrollFade Component Guide - Usage guide
- Scroll Patterns - Common patterns