Scroll Area
A scroll area is a fixed-size region whose content scrolls inside it. Its scrollbars follow the theme instead of changing with each browser and system.
import { ScrollArea } from "@/components/ui/scroll-area";
import { Separator } from "@/components/ui/separator";
const tags = Array.from({ length: 30 }, (_, index) => `v1.${30 - index}.0`);
const tiles = Array.from({ length: 10 }, (_, index) => index + 1);
export default function ScrollAreaDemo() {
return (
<>
<ScrollArea className="h-56 w-48">
<div className="p-4">
<h4 className="mb-3 text-sm font-medium text-text-secondary">Releases</h4>
{tags.map((tag, index) => (
<div key={tag}>
<p className="py-2 font-mono text-sm">{tag}</p>
{index < tags.length - 1 && <Separator />}
</div>
))}
</div>
</ScrollArea>
<ScrollArea className="w-72 max-w-full">
<div className="flex w-max gap-3 p-4">
{tiles.map((tile) => (
<div
key={tile}
className="flex size-24 shrink-0 items-center justify-center rounded-md border border-border bg-surface font-mono text-sm text-text-secondary"
>
{tile}
</div>
))}
</div>
</ScrollArea>
</>
);
}The code
The component is a single file, read from the repository when the site is built. Press Show code to see all of it.
import type { ComponentProps } from "react";
import { ScrollArea as BaseScrollArea } from "@base-ui/react/scroll-area";
import { cx } from "@/lib/cx";
import type { WithClass } from "@/lib/types";
// Scrollbars that stay out of the way: they are invisible until you hover the area or scroll,
// and only appear in a direction that actually overflows. Give the area a size with classes
// (h-48 w-64), and put your content inside. The viewport can be focused, so the keyboard can
// scroll it with the arrow keys.
const viewportRing =
"outline-hidden focus-visible:outline-2 focus-visible:outline-solid focus-visible:-outline-offset-2 focus-visible:outline-text";
function Scrollbar({ orientation }: { orientation: "vertical" | "horizontal" }) {
return (
<BaseScrollArea.Scrollbar
orientation={orientation}
className="flex touch-none p-0.5 opacity-0 transition-opacity duration-150 select-none data-[hovering]:opacity-100 data-[scrolling]:opacity-100 data-[orientation=horizontal]:h-3 data-[orientation=horizontal]:flex-col data-[orientation=vertical]:w-3 motion-reduce:transition-none"
>
<BaseScrollArea.Thumb className="relative flex-1 rounded-full bg-text-secondary" />
</BaseScrollArea.Scrollbar>
);
}
export function ScrollArea({
className,
children,
...props
}: WithClass<Omit<ComponentProps<typeof BaseScrollArea.Root>, "children">> & { children?: React.ReactNode }) {
return (
<BaseScrollArea.Root className={cx("relative overflow-hidden rounded-lg border border-border", className)} {...props}>
<BaseScrollArea.Viewport className={cx("size-full rounded-[inherit]", viewportRing)}>
<BaseScrollArea.Content>{children}</BaseScrollArea.Content>
</BaseScrollArea.Viewport>
<Scrollbar orientation="vertical" />
<Scrollbar orientation="horizontal" />
<BaseScrollArea.Corner />
</BaseScrollArea.Root>
);
}Props
| Prop | Type | Default | What it does |
|---|---|---|---|
className | string | — | Give the area a size with it, such as h-56 w-48. Without a size it grows to fit its content and nothing scrolls. |
children | ReactNode | — | The content that scrolls. It can be taller, wider or both. |
Every other prop goes to the Base UI ScrollArea underneath.
The thinking
Scrolling inside a box is sometimes the right call: a long list in a side panel, a row of cards, a code listing. The box needs to say that it scrolls.
- Built on Base UI: Base UI draws its own scrollbars over a native scrolling viewport. The scrolling itself is the browser's, so wheel, touch, momentum and keyboard all behave as people expect.
- Both directions: A vertical and a horizontal scrollbar are built in. Each one only appears when the content overflows in that direction.
- Same look everywhere: Native scrollbars differ between Windows, macOS and each browser. These are the same thick and the same color everywhere.
Interface design
- Border: A hairline border and the lg radius, so the edge of the area is clear.
- Quiet scrollbars: They are invisible until you hover the area or scroll it, then fade in. The thumb is a rounded bar in the secondary text color.
- Padding: The area has none. Put padding on the content inside, so the scrollbar can sit at the edge.
Interaction
- Scroll: Use the mouse wheel, a trackpad, touch, or drag the thumb.
- Keyboard: The area takes focus with Tab and shows a ring. The arrow keys, Page Up, Page Down, Home and End then scroll it.
- Motion: The scrollbars fade in over 150ms. With reduced motion turned on they appear at once.
Empathy
- Reachable by keyboard: Without focus a keyboard user can't scroll a box. That is why the area itself is a tab stop.
- Show that there is more: Because scrollbars are hidden at rest, let content run past the edge, or cut it off mid-row, so people see there is more to scroll to.
- Use sparingly: A scroll inside a page that also scrolls can trap a wheel or a thumb. Prefer the page for long content.