A customizable scroll area component with native-like scrolling and custom scrollbar styling
npm install @choice-ui/scroll-areaA high-performance custom scroll area component built with native DOM APIs instead of Radix UI. Provides native-like scrolling behavior with customizable scrollbar appearance and advanced features for optimal performance.
``tsx`
import { ScrollArea } from "@choice-ui/react"
- Native DOM Implementation: Built with native APIs for better performance and reduced bundle size
- Auto-Scrollbars: Automatically renders scrollbars based on orientation prop - no manual scrollbar setup needed
- Multiple Visibility Modes: Four scrollbar visibility types (auto, always, scroll, hover)
- Dual Scrolling Support: Supports vertical, horizontal, or both orientations
- Performance Monitoring: Built-in performance metrics and monitoring capabilities
- Virtual List Integration: Optimized for use with @tanstack/react-virtual for large datasets
- Render Props Pattern: Access to real-time scroll position data
- Dynamic Content Support: Automatically adjusts scrollbars when content changes
- Multiple Themes: Light, dark, and auto theme variants
- Nested Scrolling: Supports nested ScrollArea components
`tsx`
orientation="vertical"
>
The new auto-scrollbars feature automatically adds scrollbars based on orientation:
`tsx
{
/ Vertical scrolling - automatically adds vertical scrollbar /
}
;
className="h-40 w-48 border"
>
{
/ Both directions - automatically adds both scrollbars + corner /
}
;
className="h-40 w-48 border"
>
`
For advanced customization, manually configure scrollbars:
`tsx`
scrollbarMode="padding-y"
>
Access real-time scroll position for scroll-based animations or indicators:
`tsx`
{({ top, left }) => (
<>
Scroll Progress: {Math.round(top * 100)}%
{content}
>
)}
Optimized for large datasets using @tanstack/react-virtual:
`tsx
function VirtualScrollArea({ items }) {
const [scrollElement, setScrollElement] = useState(null)
const virtualizer = useVirtualizer({
count: items.length,
getScrollElement: () => scrollElement,
estimateSize: () => 60,
overscan: 5,
})
return (
scrollbarMode="padding-y"
>
{virtualizer.getVirtualItems().map((virtualItem) => (
key={virtualItem.key}
style={{
height: virtualItem.size,
transform: translateY(${virtualItem.start}px),
}}
>
{/ Virtual item content /}
))}
$3
Built-in performance monitoring for scroll optimization:
`tsx
import { useScrollPerformanceMonitor } from "@choice-ui/react"function MonitoredScrollArea() {
const [viewport, setViewport] = useState(null)
const metrics = useScrollPerformanceMonitor(viewport, {
enabled: true,
logInterval: 3000, // Report every 3 seconds
frameTimeThreshold: 16.67, // 60fps threshold
})
return (
{/ Performance metrics available in console /}
{heavyContent}
)
}
`Scrollbar Visibility Types
$3
Scrollbars visible when content overflows - standard web behavior.
$3
Scrollbars always visible regardless of content overflow.
$3
Scrollbars visible only when user is actively scrolling.
$3
Scrollbars visible when scrolling or hovering (macOS-like behavior).
`tsx
type="hover"
className="h-64 w-64 border"
>
{content}
`Scrollbar Modes
Different scrollbar appearance modes for various contexts:
-
default: Standard scrollbar appearance
- padding-y: Prominent vertical scrollbar
- padding-x: Prominent horizontal scrollbar
- padding-b: Enhanced both directions
- padding-t: Top-aligned large scrollbar
- padding-l: Left-aligned large scrollbar
- padding-r: Right-aligned large scrollbar`tsx
{/ Content /}
`Theme Variants
`tsx
{
/ Light theme /
}
;{/ Content /} {
/ Dark theme /
}
;{/ Content /}
{
/ Auto theme (follows system) /
}
;{/ Content /}
`Props
$3
`ts
interface ScrollAreaProps {
/* Accessibility label /
"aria-label"?: string /* ID of element providing label /
"aria-labelledby"?: string
/* Content or render prop function /
children?: React.ReactNode | ((position: ScrollPosition) => React.ReactNode)
/* Additional CSS classes /
className?: string
/* Custom class names for different parts /
classNames?: {
root?: string
viewport?: string
content?: string
scrollbar?: string
thumb?: string
corner?: string
}
/* Scroll orientation /
orientation?: "vertical" | "horizontal" | "both"
/* Scrollbar appearance mode /
scrollbarMode?:
| "default"
| "padding-y"
| "padding-x"
| "padding-b"
| "padding-t"
| "padding-l"
| "padding-r"
/* Scrollbar visibility behavior /
type?: "auto" | "always" | "scroll" | "hover"
/* Theme variant /
variant?: "auto" | "light" | "dark"
}
`$3
`ts
interface ScrollbarProps {
/* Scrollbar orientation /
orientation?: "vertical" | "horizontal" /* Additional CSS classes /
className?: string
}
`$3
`ts
interface ThumbProps {
/* Thumb orientation /
orientation?: "vertical" | "horizontal" /* Additional CSS classes /
className?: string
}
`Component Structure
The ScrollArea component is composed of several sub-components:
-
ScrollArea.Viewport: The scrollable container
- ScrollArea.Content: Wrapper for the actual content
- ScrollArea.Scrollbar: The scrollbar track
- ScrollArea.Thumb: The draggable scrollbar handle
- ScrollArea.Corner: Corner piece for dual-direction scrollingPerformance Considerations
$3
- Use virtual scrolling with @tanstack/react-virtual
- Enable performance monitoring during development
- Consider
overscan values for smooth scrolling$3
- ScrollArea automatically handles content size changes
- Uses MutationObserver and ResizeObserver for updates
- Scrollbar length updates dynamically
$3
- Clean up performance monitors on unmount
- Cache expensive calculations with useMemo
- Avoid unnecessary re-renders of scroll content
Best Practices
- Always specify explicit height for the container
- Use appropriate scrollbar visibility type for your use case
- Apply padding inside the viewport, not on the ScrollArea itself
- For large datasets, implement virtual scrolling
- Use render props pattern for scroll-dependent UI elements
- Enable performance monitoring during development
- Choose scrollbar modes that match your design system
Integration Examples
$3
`tsx
className="h-full"
scrollbarMode="padding-y"
>
{modalContent}
`$3
`tsx
orientation="both"
className="h-80 w-full border"
>
className="grid grid-cols-5 gap-4 p-4"
style={{ minWidth: "800px" }}
>
{items.map((item) => (
key={item.id}
className="h-40 rounded-lg border"
>
{/ Nested scrollable content /}
))}
`Accessibility
- Proper ARIA attributes for screen readers
- Keyboard navigation support
- Focus management for interactive elements
- Scroll position announcements for assistive technology
- Respects user's reduced motion preferences
Browser Support
- Modern browsers with CSS custom properties support
- Fallback behavior for older browsers
- Touch device optimization
- High DPI display support
Migration from Radix ScrollArea
The component provides the same API as Radix ScrollArea but with improved performance:
`tsx
// Old Radix approach
{/ content /}
// New approach (auto-scrollbars)
{/ content /}
``