Installation
pnpm dlx shadcn@latest add https://rdf.dev.360online.com/r/scroll-area.jsonInstalls to @/components/ui/scroll-area.
Manual installation
Install the dependencies:
pnpm add @base-ui/react@^1.8.0Copy the source into your project:
'use client'
import { ScrollArea as ScrollAreaPrimitive } from '@base-ui/react/scroll-area'
import { cn } from '@/lib/utils'
function ScrollArea({ className, children, ...props }: ScrollAreaPrimitive.Root.Props) {
return (
// `overflow-hidden` lets the root shrink below its content. Without it, a `flex-1` scroll area in a flex column
// grows with its content and never scrolls. The viewport's focus ring is inset, so it isn't clipped.
<ScrollAreaPrimitive.Root data-slot="scroll-area" className={cn('relative overflow-hidden', className)} {...props}>
<ScrollAreaPrimitive.Viewport
data-slot="scroll-area-viewport"
className="size-full rounded-[inherit] transition-[color,box-shadow] outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-inset"
>
{children}
</ScrollAreaPrimitive.Viewport>
<ScrollBar />
<ScrollAreaPrimitive.Corner />
</ScrollAreaPrimitive.Root>
)
}
function ScrollBar({ className, orientation = 'vertical', ...props }: ScrollAreaPrimitive.Scrollbar.Props) {
return (
<ScrollAreaPrimitive.Scrollbar
data-slot="scroll-area-scrollbar"
data-orientation={orientation}
orientation={orientation}
className={cn(
'flex touch-none p-px transition-colors select-none data-horizontal:h-2.5 data-horizontal:flex-col data-horizontal:border-t data-horizontal:border-t-transparent data-vertical:h-full data-vertical:w-2.5 data-vertical:border-l data-vertical:border-l-transparent',
className,
)}
{...props}
>
<ScrollAreaPrimitive.Thumb data-slot="scroll-area-thumb" className="relative flex-1 rounded-full bg-border" />
</ScrollAreaPrimitive.Scrollbar>
)
}
export { ScrollArea, ScrollBar }A custom scrollable area component with styled scrollbars and smooth scrolling behavior. Built on Base UI ScrollArea with accessibility and keyboard navigation support.
Features
- Custom styled scrollbars that match your design system
- Vertical scrollbar by default; add
<ScrollBar orientation="horizontal" />for horizontal scrolling - Scrollbars are shown whenever the content overflows, so it is always clear there is more to see
- The viewport becomes focusable when its content overflows, so it can be scrolled with the keyboard, with a focus ring
- Native scrolling with touch, mouse wheel and keyboard
data-slotattributes for styling hooks- Built on top of Base UI ScrollArea primitives
Usage
import { ScrollArea, ScrollBar } from '@/components/ui/scroll-area'
// Basic vertical scrolling
<ScrollArea className="h-72 w-48 rounded-md border">
<div className="p-4">
<h4 className="mb-4 text-sm font-medium">Content</h4>
{/* Your scrollable content */}
</div>
</ScrollArea>
// Horizontal scrolling
<ScrollArea className="w-96 rounded-md border whitespace-nowrap">
<div className="flex w-max gap-4 p-4">
{/* Horizontal content */}
</div>
<ScrollBar orientation="horizontal" />
</ScrollArea>Examples
Default
Today's entries in the public diary, scrolling vertically.
Horizontal Scroll
A row of municipal facilities that scrolls sideways.
Both Directions
A wide waste collection schedule that overflows in both directions, with a vertical and a horizontal scrollbar.
Long Text
A privacy notice. The scrollbar is visible as long as there is more to read.
Activity Log
A case history inside a card, where only the list scrolls.
Fill Remaining Height
In a flex column with a fixed height, give the scroll area flex-1 and it takes the space between the header and the
footer. The root has overflow-hidden, so it can shrink below its content and scroll instead of growing with it.
Props
ScrollArea
- Extends Base UI ScrollArea.Root props
overflowEdgeThreshold?: number- Distance in px before thedata-overflow-*-start/endattributes are setclassName?: string- Additional CSS classes, applied to the rootchildren: React.ReactNode- The scrollable content- Renders a vertical
ScrollBarautomatically
ScrollBar
- Extends Base UI ScrollArea.Scrollbar props
orientation?: "vertical" | "horizontal"- Scrollbar orientation (default: "vertical")className?: string- Additional CSS classes for custom stylingkeepMounted?: boolean- Keep the scrollbar mounted when the content doesn't overflow
Styling
Each part has a data-slot attribute you can target:
scroll-area- The root element (withdata-has-overflow-x/y,data-overflow-*-start/endanddata-scrolling)scroll-area-viewport- The scrolling viewportscroll-area-scrollbar- The scrollbar track (withdata-orientation,data-hoveringanddata-scrolling)scroll-area-thumb- The scrollbar thumb (usesbg-border)
Accessibility
The Scroll Area component follows WCAG 2.1 guidelines and provides:
- Keyboard navigation support:
Arrow keys: Scroll in small incrementsPage Up/Down: Scroll in large incrementsHome/End: Scroll to beginning/endSpace: Scroll down one pageTab: Navigate to the scrollable area (the viewport is focusable while its content overflows)
- Visible focus ring on the viewport
- Touch and mouse wheel interaction support
- High contrast mode compatibility
Best Practices
-
Content Management
- Set appropriate dimensions for your scroll area: a fixed height such as
h-72, orflex-1in a flex column with a bounded height - Ensure content has proper padding and spacing
- Consider loading states for dynamic content
- Use appropriate overflow handling for nested elements
- Set appropriate dimensions for your scroll area: a fixed height such as
-
Performance
- Avoid putting too many interactive elements in scroll areas
- Consider virtualization for very long lists
- Optimize content rendering during scroll events
-
User Experience
- Provide visual feedback for scrollable areas
- Use horizontal scrolling sparingly - prefer wrapping content
- Ensure scrollbars are discoverable but not intrusive
- Test on different devices and input methods
-
Responsive Design
- Test scroll behavior on touch devices
- Ensure adequate touch targets for scrollbars
- Consider different scroll behaviors for mobile vs desktop
- Use appropriate sizing for different screen sizes
-
Accessibility
- Provide alternative navigation methods for complex content
- Ensure sufficient color contrast for scrollbars
- Test with keyboard navigation thoroughly
- Verify screen reader compatibility
Advanced Usage
Programmatic Scrolling and Scroll Events
The element that actually scrolls is the viewport, not the root. A ref on ScrollArea points to the root,
so query the viewport through its data-slot to scroll it or listen for scroll events.
import { useEffect, useRef } from 'react'
import { ScrollArea } from '@/components/ui/scroll-area'
function MyComponent() {
const rootRef = useRef<HTMLDivElement>(null)
const getViewport = () =>
rootRef.current?.querySelector<HTMLDivElement>('[data-slot="scroll-area-viewport"]')
const scrollToTop = () => getViewport()?.scrollTo({ top: 0, behavior: 'smooth' })
useEffect(() => {
const viewport = getViewport()
if (!viewport) return
const onScroll = () => {
const { scrollTop, scrollHeight, clientHeight } = viewport
if (scrollTop + clientHeight >= scrollHeight - 10) {
// Load more content
}
}
viewport.addEventListener('scroll', onScroll)
return () => viewport.removeEventListener('scroll', onScroll)
}, [])
return (
<div>
<button type="button" onClick={scrollToTop}>Top</button>
<ScrollArea ref={rootRef} className="h-72 w-48">
{/* Content */}
</ScrollArea>
</div>
)
}Common Use Cases
- Content Lists: Long lists of items, tags, or options
- Chat Interfaces: Message history with automatic scrolling
- Code Editors: Syntax-highlighted code with horizontal overflow
- File Explorers: Hierarchical file trees and folder structures
- Documentation: Long-form content with table of contents
- Data Tables: Large datasets with horizontal and vertical scrolling
- Image Galleries: Horizontal scrolling image carousels
- Settings Panels: Long configuration forms and option lists
Related Components
- Card
- Sheet
- Dialog
- Resizable
- Table