Use Scroll Area when a panel needs its own scrollable content. Set a height or maximum height so there is something to scroll. Do not replace ordinary page scrolling without a clear layout need.
Live preview
import { ScrollArea } from "@/components/ui/scroll-area";
const updates = [
["Button reference", "Variants and navigation guidance reviewed"],
["Sidebar", "Mobile hook and manual install files verified"],
["Theme export", "Customizer output matches registry tokens"],
["Dialog", "Focus return and local submission tested"],
["Calendar", "Range selection consumer build passed"],
["Templates", "Next.js App Router pages generated"],
] as const;
export default function ScrollAreaDemo() {
return (
<ScrollArea
className="h-64 w-full max-w-sm rounded-base border-2 border-border bg-secondary-background shadow-shadow"
aria-label="Recent registry activity"
>
<div className="p-4">
<div className="mb-3 border-b-2 border-border pb-3">
<p className="text-xs font-heading uppercase tracking-wide text-foreground/70">
Workspace
</p>
<h3 className="text-lg font-heading">Recent activity</h3>
</div>
<ul className="divide-y-2 divide-border">
{updates.map(([title, detail]) => (
<li key={title} className="py-3 first:pt-0 last:pb-0">
<p className="font-heading">{title}</p>
<p className="mt-1 text-sm leading-5 text-foreground/75">{detail}</p>
</li>
))}
</ul>
</div>
</ScrollArea>
);
}
#Installation
Complete the base setup. For manual installation:
First time? Install the shared base once.
npx shadcn@latest add https://neobrutal-ui.andongmin.com/r/scroll-area.jsonnpm install @base-ui/react@^1.9.0Create components/ui/scroll-area.tsx:
"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 (
<ScrollAreaPrimitive.Root
data-slot="scroll-area"
{...props}
className={(state) =>
cn(
"group/scroll-area relative overflow-hidden",
typeof className === "function" ? className(state) : className,
)
}
>
<ScrollAreaPrimitive.Viewport
data-slot="scroll-area-viewport"
className="h-full w-full rounded-[inherit] font-base transition-[color,box-shadow] outline-none focus-visible:ring-1 focus-visible:ring-ring"
>
<ScrollAreaPrimitive.Content data-slot="scroll-area-content">
{children}
</ScrollAreaPrimitive.Content>
</ScrollAreaPrimitive.Viewport>
<ScrollBar />
<ScrollAreaPrimitive.Corner data-slot="scroll-area-corner" />
</ScrollAreaPrimitive.Root>
);
}
function ScrollBar({
className,
orientation = "vertical",
...props
}: ScrollAreaPrimitive.Scrollbar.Props) {
return (
<ScrollAreaPrimitive.Scrollbar
data-slot="scroll-area-scrollbar"
orientation={orientation}
{...props}
className={(state) =>
cn(
"flex touch-none p-px opacity-0 transition-[color,background-color,border-color,opacity] duration-150 select-none data-hovering:opacity-100 data-scrolling:opacity-100 data-[orientation=horizontal]:h-2.5 data-[orientation=horizontal]:flex-col data-[orientation=horizontal]:border-t data-[orientation=horizontal]:border-t-transparent data-[orientation=vertical]:h-full data-[orientation=vertical]:w-2.5 data-[orientation=vertical]:border-l data-[orientation=vertical]:border-l-transparent",
typeof className === "function" ? className(state) : className,
)
}
>
<ScrollAreaPrimitive.Thumb
data-slot="scroll-area-thumb"
className="relative flex-1 rounded-full bg-border"
/>
</ScrollAreaPrimitive.Scrollbar>
);
}
export { ScrollArea, ScrollBar };
#Usage
import { ScrollArea } from "@/components/ui/scroll-area";
export default function ActivityList() {
return (
<ScrollArea
className="h-48 w-full max-w-sm rounded-base border-2 border-border"
aria-label="Recent activity"
>
<ul className="space-y-3 p-4">
{Array.from({ length: 12 }, (_, index) => (
<li key={index}>Workspace update {index + 1}</li>
))}
</ul>
</ScrollArea>
);
}#API reference
| Prop / part | Purpose | Default |
|---|---|---|
ScrollArea.className | Root bounds and layout | Unbounded unless styled |
ScrollArea.overflowEdgeThreshold | Threshold for native overflow-edge state | 0 |
ScrollBar.orientation | Vertical or horizontal scrollbar | vertical |
ScrollBar.keepMounted | Keep a scrollbar mounted without overflow | false |
| Children | Content inside the viewport | Required for useful scrolling |
#Accessibility
Ensure keyboard users can reach and scroll the region. Name substantial regions and do not trap wheel or touch scrolling unnecessarily. Horizontal content needs a matching horizontal scrollbar and a clear cue that more content exists.