Scroll Area
A scrollable container with custom scrollbars and optional scroll shadow indicators.
Overview
The ScrollArea component provides a scrollable viewport with custom scrollbars and optional edge shadows. It does not paint its own surface by default; put it inside the surface that owns the frame.
Scrollbar styling is surface-aware. Tracks land two levels above the current surface and thumbs land four levels above the current surface. Scroll shadows default to the current surface color, so a scroll area inside Card automatically fades to the card surface.
Usage
import { Card } from "@tilt-legal/cubitt-components/card";
import {
ScrollArea,
ScrollAreaContent,
} from "@tilt-legal/cubitt-components/scroll-area";<Card className="h-72 w-56">
<ScrollArea className="h-full w-full">
<ScrollAreaContent className="p-4">
{/* Scrollable content */}
</ScrollAreaContent>
</ScrollArea>
</Card>Examples
Basic
A vertical scroll area inside a card. The card owns the surface and the scroll content owns the padding.
import { Card } from "@tilt-legal/cubitt-components/card";
import {
ScrollArea,
ScrollAreaContent,
} from "@tilt-legal/cubitt-components/scroll-area";
import { Separator } from "@tilt-legal/cubitt-components/separator";
export default function Example() {
const tags = Array.from(
{ length: 50 },
(_, index) => `v1.2.0-beta.${50 - index}`
);
return (
<Card className="h-72 w-56">
<ScrollArea className="h-full w-full">
<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>
</Card>
);
}Horizontal
Set orientation="horizontal" for horizontal overflow.
import type { CSSProperties } from "react";
import { Card } from "@tilt-legal/cubitt-components/card";
import {
ScrollArea,
ScrollAreaContent,
} from "@tilt-legal/cubitt-components/scroll-area";
const scrollbarInsetStyle = {
"--scrollbar-inset-left": "1rem",
"--scrollbar-inset-right": "1rem",
} as CSSProperties;
export default function Example() {
const artworks = [
{ artist: "Ornella Binni" },
{ artist: "Tom Byrom" },
{ artist: "Vladimir Malyavko" },
];
return (
<Card className="h-52 w-full max-w-xl">
<ScrollArea
className="h-full w-full"
orientation="horizontal"
style={scrollbarInsetStyle}
>
<ScrollAreaContent className="flex w-max space-x-4 p-4">
{artworks.map((artwork) => (
<figure className="shrink-0" key={artwork.artist}>
<div className="h-36 w-48 rounded-lg bg-surface-5" />
<figcaption className="pt-2 text-fg-2 text-xs">
Photo by{" "}
<span className="font-semibold text-fg-1">
{artwork.artist}
</span>
</figcaption>
</figure>
))}
</ScrollAreaContent>
</ScrollArea>
</Card>
);
}Both Directions
Set orientation="both" when content can overflow on both axes.
import type { CSSProperties } from "react";
import { Card } from "@tilt-legal/cubitt-components/card";
import {
ScrollArea,
ScrollAreaContent,
} from "@tilt-legal/cubitt-components/scroll-area";
const scrollbarInsetStyle = {
"--scrollbar-inset-left": "1rem",
"--scrollbar-inset-right": "1rem",
} as CSSProperties;
export default function Example() {
return (
<Card className="h-72 w-full max-w-xl">
<ScrollArea
className="h-full w-full"
orientation="both"
style={scrollbarInsetStyle}
>
<ScrollAreaContent className="p-4">
<div className="w-[600px] space-y-4">
{Array.from({ length: 20 }).map((_, index) => (
<div className="flex gap-4" key={index}>
<div className="h-12 w-48 shrink-0 rounded-lg bg-surface-4" />
<div className="h-12 w-48 shrink-0 rounded-lg bg-surface-4" />
<div className="h-12 w-48 shrink-0 rounded-lg bg-surface-4" />
</div>
))}
</div>
</ScrollAreaContent>
</ScrollArea>
</Card>
);
}Scroll Shadows
Use scrollShadow to show edge gradients when more content is available.
import type { CSSProperties } from "react";
import { Card } from "@tilt-legal/cubitt-components/card";
import {
ScrollArea,
ScrollAreaContent,
} from "@tilt-legal/cubitt-components/scroll-area";
const scrollbarInsetStyle = {
"--scrollbar-inset-left": "1rem",
"--scrollbar-inset-right": "1rem",
} as CSSProperties;
export default function Example() {
return (
<Card className="h-72 w-full max-w-xl">
<ScrollArea
className="h-full w-full"
orientation="both"
scrollShadow="both"
style={scrollbarInsetStyle}
>
<ScrollAreaContent className="p-4">
{/* Wide and tall content */}
</ScrollAreaContent>
</ScrollArea>
</Card>
);
}Custom Shadow
Fine-tune shadow size, opacity, stop position, snap behavior, and color.
import { Card } from "@tilt-legal/cubitt-components/card";
import {
ScrollArea,
ScrollAreaContent,
} from "@tilt-legal/cubitt-components/scroll-area";
export default function Example() {
return (
<Card className="h-72 w-72">
<ScrollArea
className="h-full w-full"
scrollShadow={{
vertical: {
size: "80px",
stopPercent: 60,
stopOpacity: 90,
color: "var(--surface-3)",
},
}}
>
<ScrollAreaContent className="p-4">
{/* Scrollable content */}
</ScrollAreaContent>
</ScrollArea>
</Card>
);
}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" | ScrollShadowObjectConfig | "none" | Optional edge shadow configuration. |
className | string | — | Additional classes for the viewport. |
ScrollShadowConfig
| Property | Type | Default | Description |
|---|---|---|---|
size | string | "40px" | Size of the shadow gradient. |
color | string | Current surface | CSS color used for the gradient. |
stopPercent | number | 0 | Percentage where the solid color ends and fading begins. |
stopOpacity | number | 100 | Opacity of the intermediate color stop before fading to transparent. |
snap | boolean | false | When true, the shadow appears at full size instead of growing gradually. |
ScrollShadowObjectConfig
The object configuration supports axis shortcuts or individual sides. Do not mix an axis shortcut with that axis' side-specific keys.
| 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. |
ScrollAreaContent
Container for the scrollable content.
| Prop | Type | Default | Description |
|---|---|---|---|
className | string | — | Additional classes for the content container. |
ScrollBar
Custom scrollbar component used internally by ScrollArea.
| Prop | Type | Default | Description |
|---|---|---|---|
orientation | "vertical" | "horizontal" | "vertical" | Direction of the scrollbar. |
className | string | — | Additional classes for the track. |
CSS Custom Properties
Scrollbar inset variables are orientation-specific. Vertical scrollbars read the top and bottom variables; horizontal scrollbars read the left and right variables.
| Variable | Default | Description |
|---|---|---|
--scrollbar-inset-top | 1rem | Top inset used by vertical scrollbar tracks. |
--scrollbar-inset-bottom | 1rem | Bottom inset used by vertical scrollbar tracks. |
--scrollbar-inset-left | 0rem | Left inset used by horizontal scrollbar tracks. |
--scrollbar-inset-right | 0rem | Right inset used by horizontal scrollbar tracks. |