A scroll container with thin, themed scrollbars and optional fading edges. Native scrolling (wheel, touch, keyboard and scroll snapping all work as usual), styled with CSS only, vertically, horizontally or both, with always-visible, hover-only or hidden scrollbars.
Scroll Area: a region inside a page that scrolls on its own, such as a list in a card, a row of cards, or a wide table, where the default scrollbar looks heavy.
Let the page scroll when the content is the main thing on the page. Nested scrolling regions are harder to use, especially on touch devices.
Carousel: horizontally scrolling slides with previous and next buttons.
<ScrollArea> {/* the scroll container, tabIndex=0 */} {children}</ScrollArea>
Part
Renders
Notes
ScrollArea
<div>
overflow: auto on the chosen axes, a thin scrollbar via scrollbar-width and scrollbar-color (with ::-webkit-scrollbar styles for older WebKit), and overscroll-behavior: contain. Focusable, with a focus ring.
There's no custom scrollbar element: the browser draws the scrollbar, so platform behaviors like overlay scrollbars on macOS and momentum scrolling are kept.
fade masks the edges that have more content beyond them, and updates as you scroll and when the content resizes. Pass a number for the fade size in px (default 24). The mask covers everything on the element, borders included, so put borders and backgrounds on a wrapper.
1. Acceptance
By creating an account you agree to these terms and to our privacy policy. If you use the service on behalf of a company, you confirm you can bind it to these terms.
2. Your account
Keep your password secure and tell us right away about any unauthorized use. You're responsible for activity under your account.
3. Billing
Paid plans renew automatically each month or year. You can cancel at any time; access continues until the end of the current period.
4. Your content
You own what you upload. You give us permission to host and process it only to run the service for you.
5. Acceptable use
Don't use the service to break the law, send spam, or interfere with other customers. We may suspend accounts that do.
6. Termination
You can close your account from settings. We'll keep an export of your data available for 30 days.
import { ScrollArea } from "@/components/ui/scroll-area";const terms = [ [ "1. Acceptance", "By creating an account you agree to these terms and to our privacy policy. If you use the service on behalf of a company, you confirm you can bind it to these terms.", ], [ "2. Your account", "Keep your password secure and tell us right away about any unauthorized use. You're responsible for activity under your account.", ], [ "3. Billing", "Paid plans renew automatically each month or year. You can cancel at any time; access continues until the end of the current period.", ], [ "4. Your content", "You own what you upload. You give us permission to host and process it only to run the service for you.", ], [ "5. Acceptable use", "Don't use the service to break the law, send spam, or interfere with other customers. We may suspend accounts that do.", ], [ "6. Termination", "You can close your account from settings. We'll keep an export of your data available for 30 days.", ],];export default function ScrollAreaFade() { return ( // The mask fades everything on the scroll element, so the border lives on a wrapper. <div className="w-full max-w-md rounded-lg border bg-card"> <ScrollArea fade aria-label="Terms of service" className="h-60"> <div className="flex flex-col gap-4 p-4 text-sm leading-relaxed"> {terms.map(([title, body]) => ( <section key={title}> <h4 className="font-medium">{title}</h4> <p className="mt-1 text-muted-foreground">{body}</p> </section> ))} </div> </ScrollArea> </div> );}
scrollbar="thin" (default) is always visible, hover shows it only while the pointer is over the area or it has keyboard focus, and hidden removes it while keeping wheel, touch and keyboard scrolling.
The scroll area has tabIndex={0} by default, so keyboard users can focus it and scroll with the arrow keys, Page Up/Down, Home and End. Pass tabIndex={-1} if the content is already fully reachable by tabbing through focusable children.
Because it's focusable, give it a name with aria-label or aria-labelledby, and consider role="region" for large, landmark-like areas.
With scrollbar="hidden", nothing shows that the area scrolls. Use it only when the content makes it obvious (a cut-off row of chips, a fade edge).
Fades are visual only and respect the content beneath them; they don't block pointer events.
The tailwind-variants function behind ScrollArea. Apply it to any element to get the same scrollbars without the fade logic, for example a <pre> or a <table> wrapper.