Appearance
ScrollFade API
Complete type definitions and prop reference for the ScrollFade component system.
ScrollFade
Low-level gradient fade overlay component.
Import
tsx
import { ScrollFade, type ScrollFadeProps } from '@/shared/ui/components/scroll/ScrollFade'Props
ScrollFadeProps
| Prop | Type | Default | Description |
|---|---|---|---|
position | 'top' | 'bottom' | Required | Position of the fade effect |
size | 'sm' | 'md' | 'lg' | 'md' | Height of the fade gradient |
visible | boolean | true | Whether to show the fade (controls opacity) |
className | string | - | Additional CSS classes |
zIndex | number | 10 | Z-index for layering |
gradient | GradientConfig | - | Custom gradient configuration |
GradientConfig
ts
interface GradientConfig {
/** Starting color (e.g., 'rgb(255 255 255)', 'hsl(var(--card))') */
from: string
/** Middle color (optional, defaults to from with 80% opacity) */
via?: string
/** Ending color is always transparent */
}Size Variants
| Size | Height | Use Case |
|---|---|---|
sm | h-8 (32px) | Compact UIs, dropdowns |
md | h-12 (48px) | Standard lists, cards |
lg | h-16 (64px) | Large containers, modals |
Features
- Sticky positioning: Stays at top/bottom of container
- Gradient background: Smooth fade from solid to transparent
- Backdrop blur: Subtle
blur(2px)effect - Masked blur: Blur fades out with gradient
- Framer Motion: Smooth opacity animations
- Negative margins: Overlays content for seamless effect
Example
tsx
import { ScrollFade } from '@/shared/ui/components/scroll/ScrollFade'
// Basic usage
<ScrollFade position="top" visible={showTopFade} />
// Custom size
<ScrollFade position="bottom" size="lg" visible={showBottomFade} />
// Custom gradient for card background
<ScrollFade
position="top"
visible={showTopFade}
gradient={{
from: 'hsl(var(--card))',
via: 'hsl(var(--card) / 0.8)',
}}
/>
// Custom z-index
<ScrollFade
position="bottom"
visible={showBottomFade}
zIndex={20}
/>ScrollFadeContainer
All-in-one wrapper with automatic scroll detection.
Import
tsx
import { ScrollFadeContainer, type ScrollFadeContainerProps } from '@/shared/ui/components/scroll/ScrollFadeContainer'Props
ScrollFadeContainerProps
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | Required | Child content (should include scrollable element) |
fades | 'top' | 'bottom' | 'both' | 'both' | Which fades to show |
fadeSize | 'sm' | 'md' | 'lg' | 'md' | Size of fade gradients |
className | string | - | Additional CSS classes for container |
scrollOptions | UseScrollFadeOptions | - | Options for scroll detection hook |
fadeZIndex | number | 10 | Z-index for fade overlays |
fadeGradient | GradientConfig | - | Custom gradient configuration |
fadeClassName | string | - | Additional CSS classes for fade elements |
UseScrollFadeOptions
ts
interface UseScrollFadeOptions {
/** Threshold in pixels before showing fade (default: 10) */
threshold?: number
/** Debounce delay in ms (default: 100) */
debounce?: number
}Features
- Automatic detection: Uses
useScrollFadehook internally - Ref management: Handles container ref automatically
- Flexible fades: Show top, bottom, or both
- Pass-through props: All fade customization options available
Examples
Basic Usage
tsx
import { ScrollFadeContainer } from '@/shared/ui/components/scroll/ScrollFadeContainer'
import SimpleBar from 'simplebar-react'
<ScrollFadeContainer>
<SimpleBar className="h-[400px]">
<div className="space-y-2 p-4">
{items.map((item) => (
<div key={item.id}>{item.name}</div>
))}
</div>
</SimpleBar>
</ScrollFadeContainer>Custom Fade Size
tsx
<ScrollFadeContainer fadeSize="sm">
<div className="h-[300px] overflow-y-auto">
{content}
</div>
</ScrollFadeContainer>Top or Bottom Only
tsx
// Only show top fade
<ScrollFadeContainer fades="top">
{content}
</ScrollFadeContainer>
// Only show bottom fade
<ScrollFadeContainer fades="bottom">
{content}
</ScrollFadeContainer>Custom Gradient
tsx
<ScrollFadeContainer
fadeGradient={{
from: 'hsl(var(--card))',
via: 'hsl(var(--card) / 0.8)',
}}
>
<div className="bg-card rounded-lg">
{content}
</div>
</ScrollFadeContainer>Custom Scroll Options
tsx
<ScrollFadeContainer
scrollOptions={{
threshold: 20, // Show fade when 20px from edge
debounce: 150, // Debounce scroll events by 150ms
}}
>
{content}
</ScrollFadeContainer>useScrollFade Hook
Hook for detecting scroll position and determining fade visibility.
Import
tsx
import { useScrollFade, type UseScrollFadeOptions } from '@/shared/ui/components/scroll/useScrollFade'Signature
ts
function useScrollFade(
ref: RefObject<HTMLElement>,
options?: UseScrollFadeOptions
): {
showTop: boolean
showBottom: boolean
}Parameters
| Parameter | Type | Description |
|---|---|---|
ref | RefObject<HTMLElement> | Ref to scrollable container |
options | UseScrollFadeOptions | Optional configuration |
Returns
ts
interface UseScrollFadeReturn {
/** Whether to show top fade */
showTop: boolean
/** Whether to show bottom fade */
showBottom: boolean
}Options
ts
interface UseScrollFadeOptions {
/** Threshold in pixels before showing fade (default: 10) */
threshold?: number
/** Debounce delay in ms (default: 100) */
debounce?: number
}Features
- Automatic detection: Monitors scroll position and container size
- Debounced: Prevents excessive updates
- Resize aware: Updates on container resize
- Threshold support: Configurable trigger distance
Example
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, {
threshold: 20,
debounce: 150,
})
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>
)
}Complete Example
tsx
import { ScrollFadeContainer } from '@/shared/ui/components/scroll/ScrollFadeContainer'
import { Card, CardHeader, CardTitle, CardContent } from '@/shadcn/components/ui/card'
import SimpleBar from 'simplebar-react'
function ActivityFeed({ activities }) {
return (
<Card>
<CardHeader>
<CardTitle>Recent Activity</CardTitle>
</CardHeader>
<CardContent>
<ScrollFadeContainer
fadeSize="md"
fadeGradient={{
from: 'hsl(var(--card))',
via: 'hsl(var(--card) / 0.8)',
}}
scrollOptions={{
threshold: 15,
debounce: 100,
}}
>
<SimpleBar className="h-[400px]">
<div className="space-y-3 pr-4">
{activities.map((activity) => (
<div
key={activity.id}
className="p-3 bg-muted rounded-md"
>
<p className="text-sm font-medium">
{activity.user}
</p>
<p className="text-sm text-muted-foreground">
{activity.message}
</p>
<p className="text-xs text-muted-foreground mt-1">
{activity.timestamp}
</p>
</div>
))}
</div>
</SimpleBar>
</ScrollFadeContainer>
</CardContent>
</Card>
)
}Styling
Default Gradient
Top Fade:
css
background-image: linear-gradient(
to bottom,
hsl(var(--background)),
hsl(var(--background) / 0.8),
transparent
);Bottom Fade:
css
background-image: linear-gradient(
to top,
hsl(var(--background)),
hsl(var(--background) / 0.8),
transparent
);Custom Gradient
tsx
// For card backgrounds
<ScrollFade
gradient={{
from: 'hsl(var(--card))',
via: 'hsl(var(--card) / 0.8)',
}}
/>
// For custom colors
<ScrollFade
gradient={{
from: 'rgb(255 255 255)',
via: 'rgb(255 255 255 / 0.8)',
}}
/>
// For popovers
<ScrollFade
gradient={{
from: 'hsl(var(--popover))',
}}
/>Blur Effect
css
backdrop-filter: blur(2px);
-webkit-backdrop-filter: blur(2px);
/* Masked to fade out with gradient */
mask-image: linear-gradient(to bottom, black 20%, transparent 100%);
-webkit-mask-image: linear-gradient(to bottom, black 20%, transparent 100%);Positioning
css
/* Sticky positioning for automatic scroll behavior */
position: sticky;
top: 0; /* or bottom: 0 */
/* Negative margins to overlay content */
margin-bottom: -48px; /* for top fade with size="md" */
margin-top: -48px; /* for bottom fade with size="md" */Animation
Framer Motion
tsx
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: visible ? 1 : 0 }}
transition={{ duration: 0.4, ease: "easeOut" }}
>
{/* Fade content */}
</motion.div>Timing:
- Duration: 400ms
- Easing:
easeOut - Property:
opacity
Performance
Optimizations
- Sticky positioning: No JavaScript for positioning
- CSS animations: Hardware-accelerated
- Debounced scroll: Prevents excessive updates (default: 100ms)
- Conditional rendering: Only updates when visibility changes
- Passive event listeners: Improves scroll performance
Tips
- Use smaller
fadeSizefor better performance - Increase
debouncefor less frequent updates - Use
thresholdto reduce unnecessary fade toggles
Accessibility
ARIA
tsx
<ScrollFade aria-hidden="true" />Fades are purely decorative and hidden from screen readers.
Pointer Events
css
pointer-events: none;Fades don't interfere with user interaction.
Type Definitions
ts
// ScrollFade
interface ScrollFadeProps {
position: 'top' | 'bottom'
size?: 'sm' | 'md' | 'lg'
visible?: boolean
className?: string
zIndex?: number
gradient?: {
from: string
via?: string
}
}
// ScrollFadeContainer
interface ScrollFadeContainerProps {
children: ReactNode
fades?: 'top' | 'bottom' | 'both'
fadeSize?: 'sm' | 'md' | 'lg'
className?: string
scrollOptions?: UseScrollFadeOptions
fadeZIndex?: number
fadeGradient?: ScrollFadeProps['gradient']
fadeClassName?: string
}
// useScrollFade Hook
interface UseScrollFadeOptions {
threshold?: number
debounce?: number
}
interface UseScrollFadeReturn {
showTop: boolean
showBottom: boolean
}Browser Support
- Modern browsers: Full support
- Safari: Requires
-webkit-prefixes (included) - backdrop-filter: Supported in all modern browsers
- Framer Motion: Requires JavaScript enabled