Scroll area
A native scroll container with thin overlay scrollbars and edge fades that appear only when content overflows.
disclosurenew
Preview
Scroll area preview
Live · keyboard ready
When to use
- Panels, sidebars, and popovers whose content can outgrow their box.
- Horizontal strips of cards or chips that need mouse-wheel scrolling and snap points.
- Places where platform scrollbars look heavy but native scrolling must be kept.
When not to use
- Use carousel when items should page one at a time with controls.
- Do not wrap the whole page; let the document scroll natively.
- Use data-grid for large tabular data; it virtualizes its own scroll.
shadcn CLI
Also installs (1)
- manicat-foundation
Manual install(+)
registry.jsonjson
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "scroll-area",
"type": "registry:ui",
"title": "Scroll area",
"description": "A native scroll container with thin overlay scrollbars and edge fades that appear only when content overflows.",
"categories": [
"disclosure"
],
"dependencies": [],
"registryDependencies": [
"https://manicat.viraza.net/r/manicat-foundation.json"
],
"files": [
{
"path": "registry/components/scroll-area/scroll-area.tsx",
"type": "registry:component",
"target": "@components/manicat/scroll-area/scroll-area.tsx"
},
{
"path": "registry/components/scroll-area/scroll-area.module.css",
"type": "registry:component",
"target": "@components/manicat/scroll-area/scroll-area.module.css"
}
],
"docs": "Docs and live preview: https://uiarc.dev/components/scroll-area",
"meta": {
"tier": "free",
"kind": "component",
"docs": "https://manicat.viraza.net/components/scroll-area",
"markdown": "https://uiarc.dev/components/scroll-area/markdown",
"tags": [
"disclosure",
"new"
]
}
}Usage
Install the component, then copy this into your app. Change the content and the props to fit your screen.
example.tsxtsx
"use client";
import { ScrollArea } from "@/components/manicat/scroll-area/scroll-area";
export function ScrollAreaExample() {
return (
<ScrollArea maxHeight={180} className="w-full max-w-sm rounded-[var(--radius-panel)] border border-(color:--border) p-4">
<div className="flex flex-col gap-3 text-sm">
{Array.from({ length: 12 }, (_, index) => (
<p key={index} className="text-(color:--text-muted)">
Row {index + 1}. The scrollbar fades in when you scroll and fades out when you stop.
</p>
))}
</div>
</ScrollArea>
);
}
Examples
Real screens rather than isolated props. Switch to Code to copy a whole example into your project.
Activity log A bounded list with an overlay scrollbar that only shows while scrolling.
Activity log
A bounded list with an overlay scrollbar that only shows while scrolling. preview
Live · keyboard ready
activity-log-a-bounded-list-with-an-overlay-scrollbar-that-only-shows-while-scrolling-.tsxtsx
"use client";
import { ScrollArea } from "@/components/manicat/scroll-area/scroll-area";
export function ScrollAreaLogExample() {
return (
<ScrollArea maxHeight={180} className="w-full max-w-sm rounded-[var(--radius-panel)] border border-(color:--border) p-4">
<div className="flex flex-col gap-3 text-sm">
{Array.from({ length: 12 }, (_, index) => (
<p key={index} className="text-(color:--text-muted)">
09:{String(10 + index * 3).padStart(2, "0")} · Deployment #{140 + index} finished
</p>
))}
</div>
</ScrollArea>
);
}
Keyboard interactions
| Keys | Action |
|---|---|
| Tab | The viewport is focusable (tabIndex 0). |
| Arrow keys / Page Up / Page Down / Home / End / Space | Scroll natively once the viewport has focus. |
Accessibility
- Scrolling stays native, so keyboard, screen reader, and touch behavior match the platform.
- With label, the viewport is a named region; without one, give it context another way.
- The overlay tracks and thumbs are aria-hidden; the hidden native scrollbar remains the real control.
Motion
- Scrollbars fade in while scrolling or on hover and fade out after hideDelay; the thumb thickens under the pointer.
- Pressing the track pages 90% of the viewport toward the pointer with smooth scrolling, or instantly under reduced motion.
- Reduced motion removes scrollbar transitions.
Responsive behavior
- Touch keeps native momentum scrolling; the overlay bars only appear while scrolling.
- Thumbs only thicken on hover-capable fine pointers.
- Content changes and resizes are tracked with ResizeObserver and MutationObserver, so fades and thumbs stay correct as layouts reflow.
Performance
- Fades and thumb positions are written straight to the DOM on each scroll event; scrolling never re-renders React.
- Horizontal wheel conversion only captures the wheel while there is room to scroll, so the page still scrolls at the ends.
- Edge fades use CSS mask-image, which is cheap but still composited; set fade={0} in very long lists if you see cost.
Notes for AI
- Use it wherever content scrolls inside a fixed box: sidebars, panels, menus, horizontal card strips.
- Give vertical areas a height or maxHeight; the viewport scrolls only when it has a constrained size.
- Use onEdgeChange to show a "more below" affordance or to load more when bottom becomes false.