Primitives
Scroll Area
A container that scrolls its content and shows a thin scrollbar in the colors of the theme.
Primitives
A container that scrolls its content and shows a thin scrollbar in the colors of the theme.
import { ScrollArea } from 'ferry-ui'
const ENTRIES = [
'Maya Chen invited Jonas Weber',
'Invoice INV-2041 paid',
'Jonas Weber joined the workspace',
'API key "Production backend" created',
'Project Atlas renamed to Beacon',
'Priya Patel changed the plan to Pro',
'Invoice INV-2042 sent to Acme',
'Order ORD-10482 shipped',
'Maya Chen changed the role of Lucas Martin',
'API key "Staging" revoked',
'Invoice INV-2043 is overdue',
'Order ORD-10483 refunded',
'Lucas Martin left the workspace',
'Project Compass archived',
]
export default function ScrollAreaHero() {
return (
<ScrollArea
className="h-64 w-full max-w-sm rounded-lg border bg-surface-100"
viewportProps={{ tabIndex: 0, role: 'region', 'aria-label': 'Activity log' }}
>
<ul className="divide-y text-[13px] text-foreground-light">
{ENTRIES.map((entry) => (
<li key={entry} className="px-4 py-2.5">
{entry}
</li>
))}
</ul>
</ScrollArea>
)
}h-72. With no limit, the area grows with its content and does not scroll.Import the parts. ScrollArea already has a vertical scrollbar. Add a ScrollBar only for a horizontal scroll.
import { ScrollArea, ScrollBar } from 'ferry-ui'
<ScrollArea>
{/* the content */}
<ScrollBar orientation="horizontal" />
</ScrollArea>By default, the scrollbar shows while the pointer is on the area. Set type="always" to keep the scrollbar in view.
import { ScrollArea, Separator } from 'ferry-ui'
const VERSIONS = Array.from({ length: 24 }, (_, index) => `v2.${24 - index}.0`)
export default function ScrollAreaAlways() {
return (
<ScrollArea
type="always"
className="h-56 w-56 rounded-lg border bg-surface-100"
viewportProps={{ tabIndex: 0, role: 'region', 'aria-label': 'Releases' }}
>
<div className="p-4">
<div className="mb-3 mono-label">Releases</div>
{VERSIONS.map((version) => (
<div key={version}>
<div className="py-1.5 font-mono text-[13px] text-foreground-light">{version}</div>
<Separator />
</div>
))}
</div>
</ScrollArea>
)
}Add a ScrollBar with orientation="horizontal" as the last child. The area then scrolls content that is wider than it.
import { ScrollArea, ScrollBar } from 'ferry-ui'
const PROJECTS = [
{ name: 'Marketing site', tasks: 14 },
{ name: 'Billing portal', tasks: 8 },
{ name: 'Mobile app', tasks: 23 },
{ name: 'Design system', tasks: 5 },
{ name: 'Help center', tasks: 11 },
{ name: 'Partner API', tasks: 9 },
]
export default function ScrollAreaHorizontal() {
return (
<ScrollArea
type="always"
className="w-full max-w-md rounded-lg border whitespace-nowrap"
viewportProps={{ tabIndex: 0, role: 'region', 'aria-label': 'Projects' }}
>
{/* w-max: the row keeps the width of its items, wider than the area. */}
<div className="flex w-max gap-3 p-4">
{PROJECTS.map((project) => (
<div key={project.name} className="w-40 shrink-0 rounded-lg border bg-surface-100 p-3">
<div className="truncate text-sm font-medium text-foreground">{project.name}</div>
<div className="mt-1 text-xs text-foreground-lighter">
<span className="tabular">{project.tasks}</span> open tasks
</div>
</div>
))}
</div>
<ScrollBar orientation="horizontal" />
</ScrollArea>
)
}In a flex column, give the area the min-h-0 flex-1 classes. The area takes the height that the other elements leave.
import { ScrollArea } from 'ferry-ui'
const NOTIFICATIONS = [
{ who: 'Maya Chen', what: 'added a comment on the invoice INV-2041', when: '2 min' },
{ who: 'Jonas Weber', what: 'invited you to the project Beacon', when: '14 min' },
{ who: 'Priya Patel', what: 'marked the invoice INV-2042 as paid', when: '1 h' },
{ who: 'Lucas Martin', what: 'gave you the task "Update the emails"', when: '3 h' },
{ who: 'Maya Chen', what: 'created a new API key', when: '5 h' },
{ who: 'Jonas Weber', what: 'changed the plan to Pro', when: 'Yesterday' },
{ who: 'Priya Patel', what: 'closed 6 tasks in the project Atlas', when: 'Yesterday' },
{ who: 'Lucas Martin', what: 'exported the orders of September', when: '2 d' },
]
export default function ScrollAreaPanel() {
return (
<div className="flex h-80 w-full max-w-xs flex-col overflow-hidden rounded-lg border bg-surface-100">
{/* The header keeps its height. */}
<div className="flex h-11 shrink-0 items-center justify-between border-b px-4">
<span className="text-sm font-medium text-foreground">Notifications</span>
<span className="tabular text-xs text-foreground-lighter">{NOTIFICATIONS.length} new</span>
</div>
{/* The area takes the height that stays free. */}
<ScrollArea
className="min-h-0 flex-1"
viewportProps={{ tabIndex: 0, role: 'region', 'aria-label': 'Notifications' }}
>
<ul className="divide-y">
{NOTIFICATIONS.map((notification) => (
<li key={notification.what} className="flex flex-col gap-0.5 px-4 py-2.5">
<span className="text-[13px] text-foreground-light">
<span className="font-medium text-foreground">{notification.who}</span> {notification.what}
</span>
<span className="text-xs text-foreground-lighter">{notification.when}</span>
</li>
))}
</ul>
</ScrollArea>
</div>
)
}tabIndex, role and aria-label in viewportProps.viewportProps.<ScrollArea className="h-72" viewportProps={{ tabIndex: 0, role: 'region', 'aria-label': 'Activity log' }}>
{/* text or a list with no control */}
</ScrollArea>ScrollArea and ScrollBar also accept the props of their Radix UI primitive.
| Prop | Type | Default |
|---|---|---|
viewportProps | (ScrollAreaViewportProps & RefAttributes<HTMLDivElement>) | - |
Props for the scrolling viewport element (its | ||
type | "auto" | "always" | "scroll" | "hover" | - |
| See the element or the Radix UI primitive this part renders. | ||
dir | "ltr" | "rtl" | - |
| See the element or the Radix UI primitive this part renders. | ||
scrollHideDelay | number | - |
| See the element or the Radix UI primitive this part renders. | ||
asChild | boolean | - |
| See the element or the Radix UI primitive this part renders. | ||
| Prop | Type | Default |
|---|---|---|
forceMount | true | - |
| See the element or the Radix UI primitive this part renders. | ||
orientation | "horizontal" | "vertical" | vertical |
| See the element or the Radix UI primitive this part renders. | ||
asChild | boolean | - |
| See the element or the Radix UI primitive this part renders. | ||