Scroll Area
A scrollable container with custom scrollbars and optional scroll shadow indicators.
Overview
The ScrollArea component provides a scrollable container with customizable scrollbars that match your design system. Built on Base UI primitives, it supports vertical, horizontal, and bidirectional scrolling with optional scroll shadow indicators that show users when more content is available.
Usage
import { ScrollArea, ScrollAreaContent } from "@tilt-legal/cubitt-components/primitives";<ScrollArea className="h-72 w-48 rounded-md border">
<ScrollAreaContent className="p-4">
{/* Scrollable content */}
</ScrollAreaContent>
</ScrollArea>Examples
Horizontal Scroll
Scroll horizontally through content by setting orientation="horizontal".
import { ScrollArea, ScrollAreaContent } from "@tilt-legal/cubitt-components/primitives";
export default function Component() {
const artworks = [
{ artist: "Ornella Binni", art: "Reflected (2019)" },
{ artist: "Tom Byrom", art: "Mountain Mist (2020)" },
// ...more items
];
return (
<ScrollArea
className="w-96 whitespace-nowrap rounded-md border"
orientation="horizontal"
>
<ScrollAreaContent className="flex w-max space-x-4 p-4">
{artworks.map((artwork) => (
<figure key={artwork.artist} className="shrink-0">
<div className="overflow-hidden rounded-md">
<div className="h-36 w-48 bg-gradient-to-br from-muted to-muted-foreground/20" />
</div>
<figcaption className="pt-2 text-muted-foreground text-xs">
Photo by{" "}
<span className="font-semibold text-foreground">
{artwork.artist}
</span>
</figcaption>
</figure>
))}
</ScrollAreaContent>
</ScrollArea>
);
}Both Directions
Enable scrolling in both directions with orientation="both".
import { ScrollArea, ScrollAreaContent } from "@tilt-legal/cubitt-components/primitives";
export default function Component() {
return (
<ScrollArea className="h-72 w-96 rounded-md border" orientation="both">
<ScrollAreaContent className="p-4">
<div className="w-[600px]">
<h4 className="mb-4 font-medium text-sm leading-none">
Wide Content with Many Items
</h4>
<div className="space-y-4">
{Array.from({ length: 20 }).map((_, i) => (
<div key={i} className="flex gap-4">
<div className="h-12 w-48 shrink-0 rounded bg-muted" />
<div className="h-12 w-48 shrink-0 rounded bg-muted" />
<div className="h-12 w-48 shrink-0 rounded bg-muted" />
</div>
))}
</div>
</div>
</ScrollAreaContent>
</ScrollArea>
);
}Vertical Scroll Shadow
Add scroll shadow indicators to show users there's more content to scroll. The shadow fades in/out based on scroll position.
import { ScrollArea, ScrollAreaContent, Separator } from "@tilt-legal/cubitt-components/primitives";
export default function Component() {
const tags = Array.from({ length: 50 }).map(
(_, i, a) => `v1.2.0-beta.${a.length - i}`
);
return (
<ScrollArea className="h-72 w-48 rounded-md border" scrollShadow="vertical">
<ScrollAreaContent className="p-4">
<h4 className="mb-4 font-medium text-sm leading-none">Tags</h4>
{tags.map((tag) => (
<div key={tag}>
<div className="text-sm">{tag}</div>
<Separator className="my-2" />
</div>
))}
</ScrollAreaContent>
</ScrollArea>
);
}Horizontal Scroll Shadow
import { ScrollArea, ScrollAreaContent } from "@tilt-legal/cubitt-components/primitives";
export default function Component() {
return (
<ScrollArea
className="w-96 whitespace-nowrap rounded-md border"
orientation="horizontal"
scrollShadow="horizontal"
>
<ScrollAreaContent className="flex w-max space-x-4 p-4">
{/* Content items */}
</ScrollAreaContent>
</ScrollArea>
);
}Both Scroll Shadows
import { ScrollArea, ScrollAreaContent } from "@tilt-legal/cubitt-components/primitives";
export default function Component() {
return (
<ScrollArea
className="h-72 w-96 rounded-md border"
orientation="both"
scrollShadow="both"
>
<ScrollAreaContent className="p-4">
{/* Wide and tall content */}
</ScrollAreaContent>
</ScrollArea>
);
}Custom Shadow Configuration
Fine-tune the scroll shadow appearance with custom size, gradient stop percentage, and opacity values.
import { ScrollArea, ScrollAreaContent, Separator } from "@tilt-legal/cubitt-components/primitives";
export default function Component() {
const tags = Array.from({ length: 50 }).map(
(_, i, a) => `v1.2.0-beta.${a.length - i}`
);
return (
<ScrollArea
className="h-72 w-64 rounded-md border"
scrollShadow={{
vertical: { size: "80px", stopPercent: 60, stopOpacity: 90 },
}}
>
<ScrollAreaContent className="p-4">
<h4 className="mb-4 font-medium text-sm leading-none">
Custom Shadow Configuration
</h4>
<p className="mb-4 text-muted-foreground text-xs">
This scroll area has a larger shadow (80px) that stays solid for 60%
of its height, then fades at 90% opacity before becoming transparent.
</p>
{tags.map((tag) => (
<div key={tag}>
<div className="text-sm">{tag}</div>
<Separator className="my-2" />
</div>
))}
</ScrollAreaContent>
</ScrollArea>
);
}API Reference
ScrollArea
The root scrollable container component.
| Prop | Type | Default | Description |
|---|---|---|---|
orientation | "vertical" | "horizontal" | "both" | "vertical" | Direction of scrolling. |
scrollShadow | "vertical" | "horizontal" | "both" | "none" | ScrollShadowAxisConfig | ScrollShadowSideConfig | "none" | Scroll shadow indicator configuration. |
className | string | - | Additional CSS classes for the viewport. |
ScrollShadowConfig
The base configuration object for individual shadow settings:
| Property | Type | Default | Description |
|---|---|---|---|
size | string | "40px" | Size of the shadow gradient (e.g., "40px", "100px"). |
stopPercent | number | 0 | Percentage (0-100) where the solid color ends and fading begins. |
stopOpacity | number | 100 | Opacity (0-100) of the starting color before fading to transparent. |
snap | boolean | false | When true, shadow appears at full size immediately instead of growing with scroll progress. |
ScrollShadowObjectConfig
The object configuration allows flexible shadow setup with type-safe constraints:
- Vertical axis: Use either
verticalORtop/bottom(not both) - Horizontal axis: Use either
horizontalORleft/right(not both) - Cross-axis mixing: You CAN mix
verticalwithleft/right(different axes)
| Property | Type | Description |
|---|---|---|
vertical | boolean | ScrollShadowConfig | Shortcut for top and bottom shadows. |
horizontal | boolean | ScrollShadowConfig | Shortcut for left and right shadows. |
top | boolean | ScrollShadowConfig | Top shadow (cannot be used with vertical). |
bottom | boolean | ScrollShadowConfig | Bottom shadow (cannot be used with vertical). |
left | boolean | ScrollShadowConfig | Left shadow (cannot be used with horizontal). |
right | boolean | ScrollShadowConfig | Right shadow (cannot be used with horizontal). |
// Axis shortcuts
scrollShadow={{ vertical: true }}
scrollShadow={{ vertical: { size: "60px" }, horizontal: true }}
// Individual sides
scrollShadow={{ top: true, bottom: false }}
scrollShadow={{ left: { size: "40px" }, right: { size: "80px" } }}
// Cross-axis mixing (allowed - different axes)
scrollShadow={{ vertical: true, left: { size: "60px" } }}
scrollShadow={{ top: true, horizontal: { size: "40px" } }}
scrollShadow={{ top: { size: "80px" }, bottom: false, left: true, right: true }}TypeScript enforces that you cannot mix axis shortcuts with their corresponding sides:
// ✅ Valid - cross-axis mixing
scrollShadow={{ vertical: true, left: true }}
scrollShadow={{ top: true, horizontal: true }}
// ❌ Invalid - TypeScript error (same-axis conflict)
scrollShadow={{ vertical: true, top: false }} // Error!
scrollShadow={{ horizontal: true, left: true }} // Error!ScrollAreaContent
Container for the scrollable content.
| Prop | Type | Default | Description |
|---|---|---|---|
className | string | - | Additional CSS classes for the content. |
ScrollBar
Custom scrollbar component (used internally, but can be customized).
| Prop | Type | Default | Description |
|---|---|---|---|
orientation | "vertical" | "horizontal" | "vertical" | Direction of the scrollbar. |
className | string | - | Additional CSS classes for the scrollbar. |