Scroll Area
A Base UI Scroll Area for bounded overflow: popup lists, dialog bodies, wide tables, carousels and removable-chip rows. Never use it for a whole page or a reading column.
import { ScrollArea } from '@fairgarden-private/design/components/ScrollArea'
Overflow is shown by a hard 2 px rule on each side that has hidden content, never by a fade. The scrollbar is a hairline track with a rule-colored thumb that widens on hover and darkens while dragged. The viewport is focusable whenever it scrolls; give it a label to make it a named region. Set focusable={false} when the content's own items are the focus stops and scroll themselves into view, as in a tab list.
Panel, wide and rail
Panel, wide and rail
panel is the default: it scrolls vertically, its scrollbar shows on hover, focus and scroll, and it caps at half the viewport height; maxBlockSize sets another cap, such as the space a popup's positioner reports. wide scrolls horizontally with the scrollbar always visible; the Table uses it. rail is the 2 px position rail for carousels, whose thumb equals the visible fraction; pair it with Prev and Next buttons and an "n of N" count. With fitContent={false} the content keeps the viewport's width and its children overflow it, so a carousel track's slide percentages resolve against the viewport.
Panel
Wide
Rail
import { ScrollArea } from '@fairgarden-private/design/components/ScrollArea'
import styles from './kinds.module.css'
const species = [
'American Goldfinch',
'Barn Swallow',
'Belted Kingfisher',
'Black-capped Chickadee',
'Cedar Waxwing',
'Common Yellowthroat',
'Eastern Bluebird',
'Great Blue Heron',
'Indigo Bunting',
'Northern Cardinal',
'Red-winged Blackbird',
'Song Sparrow',
'Tree Swallow',
'Wood Thrush',
]
const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
/** Panel (vertical), wide (horizontal) and rail (the 2 px position rail). */
export function ScrollAreaKinds() {
return (
<div className={styles.stack}>
<section className={styles.example}>
<h3 className={styles.name}>Panel</h3>
<ScrollArea label="Species seen" className={styles.panel}>
<ul className={styles.list}>
{species.map((name) => (
<li key={name}>{name}</li>
))}
</ul>
</ScrollArea>
</section>
<section className={styles.example}>
<h3 className={styles.name}>Wide</h3>
<ScrollArea kind="wide" label="Monthly visits">
<ol className={styles.row}>
{months.map((month) => (
<li key={month} className={styles.cell}>
{month}
</li>
))}
</ol>
</ScrollArea>
</section>
<section className={styles.example}>
<h3 className={styles.name}>Rail</h3>
<ScrollArea kind="rail" label="Featured preserves">
<ol className={styles.row}>
{species.slice(0, 8).map((name) => (
<li key={name} className={styles.slide}>
{name}
</li>
))}
</ol>
</ScrollArea>
</section>
</div>
)
}
In print
Overflow is removed: the content prints at full height and width, and the scrollbars, rails and edge rules are hidden.
API Reference
A Base UI Scroll Area. Overflow is shown by a hard --border-size-2
--role-rule edge on each side with hidden content, never a fade
[D21, D68], and by a hairline track with a --role-rule thumb. Content
prints at full height with the scrollbars and edges hidden. Don’t nest
two scroll areas on the same axis.
| Prop | Type | Description |
|---|---|---|
label | | Accessible name for the scrollable region. Given, the viewport becomes a
named |
contentClassName | | Class for the content wrapper, the layout box of the children. |
fitContent | |
|
focusable | |
|
kind | |
|
maxBlockSize | | The area’s maximum block size, a CSS length such as
|
primary | | Primary Radix scale: every part (track, thumb, edges, focus ring). Never defaulted; omitted, it inherits the scope [D133]. |
secondary | | Secondary Radix scale: unused by the scroll area. Never defaulted. |
viewportRef | | Ref to the scrolling viewport, e.g. to scroll it from Prev/Next buttons. |
scrollArea
type scrollArea = scrollAreaScrollAreaProps
Props for ScrollArea: Base UI Scroll Area Root props plus the kind and color axes.
type ScrollAreaProps = ScrollAreaRootProps & {
kind?: 'panel' | 'wide' | 'rail' | null;
primary?:
| 'olive'
| 'sage'
| 'slate'
| 'sand'
| 'gray'
| 'mauve'
| 'brown'
| 'bronze'
| 'gold'
| 'red'
| 'ruby'
| 'crimson'
| 'tomato'
| 'pink'
| 'plum'
| 'indigo'
| 'iris'
| 'violet'
| 'purple'
| null;
secondary?:
| 'olive'
| 'sage'
| 'slate'
| 'sand'
| 'gray'
| 'mauve'
| 'brown'
| 'bronze'
| 'gold'
| 'red'
| 'ruby'
| 'crimson'
| 'tomato'
| 'pink'
| 'plum'
| 'indigo'
| 'iris'
| 'violet'
| 'purple'
| 'amber'
| 'blue'
| 'cyan'
| 'grass'
| 'green'
| 'jade'
| 'lime'
| 'mint'
| 'orange'
| 'sky'
| 'teal'
| 'yellow'
| null;
label?: string;
viewportRef?: React.Ref<HTMLDivElement>;
contentClassName?: string;
maxBlockSize?: string | number;
focusable?: boolean;
fitContent?: boolean;
}Specification: DESIGN-SYSTEM.md §10.19 (scroll area).