Scroll Area
A native scroll container with custom scrollbars.
import { ScrollArea } from 'base-ui-solid/scroll-area';
import styles from './index.module.css';
export default function ExampleScrollArea() {
return (
<ScrollArea.Root class={styles.ScrollArea}>
<ScrollArea.Viewport class={styles.Viewport}>
<ScrollArea.Content class={styles.Content}>
<p class={styles.Paragraph}>
Vernacular architecture is building done outside any academic tradition, and without
professional guidance. It is not a particular architectural movement or style, but
rather a broad category, encompassing a wide range and variety of building types, with
differing methods of construction, from around the world, both historical and extant and
classical and modern. Vernacular architecture constitutes 95% of the world's built
environment, as estimated in 1995 by Amos Rapoport, as measured against the small
percentage of new buildings every year designed by architects and built by engineers.
</p>
<p class={styles.Paragraph}>
This type of architecture usually serves immediate, local needs, is constrained by the
materials available in its particular region and reflects local traditions and cultural
practices. The study of vernacular architecture does not examine formally schooled
architects, but instead that of the design skills and tradition of local builders, who
were rarely given any attribution for the work. More recently, vernacular architecture
has been examined by designers and the building industry in an effort to be more energy
conscious with contemporary design and construction—part of a broader interest in
sustainable design.
</p>
</ScrollArea.Content>
</ScrollArea.Viewport>
<ScrollArea.Scrollbar class={styles.Scrollbar}>
<ScrollArea.Thumb class={styles.Thumb} />
</ScrollArea.Scrollbar>
</ScrollArea.Root>
);
}
Anatomy
Import the component and assemble its parts:
import { ScrollArea } from 'base-ui-solid/scroll-area';
<ScrollArea.Root>
<ScrollArea.Viewport>
<ScrollArea.Content />
</ScrollArea.Viewport>
<ScrollArea.Scrollbar>
<ScrollArea.Thumb />
</ScrollArea.Scrollbar>
<ScrollArea.Corner />
</ScrollArea.Root>;
Examples
Both scrollbars
Use <ScrollArea.Corner> to prevent the scrollbars from intersecting.
import { ScrollArea } from 'base-ui-solid/scroll-area';
import styles from './index.module.css';
export default function ExampleScrollAreaBoth() {
return (
<ScrollArea.Root class={styles.ScrollArea}>
<ScrollArea.Viewport class={styles.Viewport}>
<ScrollArea.Content class={styles.Content}>
<ul class={styles.Grid}>
{Array.from({ length: 100 }, (_, i) => (
<li class={styles.Item}>{i + 1}</li>
))}
</ul>
</ScrollArea.Content>
</ScrollArea.Viewport>
<ScrollArea.Scrollbar class={styles.Scrollbar}>
<ScrollArea.Thumb class={styles.Thumb} />
</ScrollArea.Scrollbar>
<ScrollArea.Scrollbar class={styles.Scrollbar} orientation="horizontal">
<ScrollArea.Thumb class={styles.Thumb} />
</ScrollArea.Scrollbar>
<ScrollArea.Corner />
</ScrollArea.Root>
);
}
Gradient scroll fade
Use the viewport overflow CSS variables to drive a CSS mask, which gradually increases the fade as the user scrolls away from the edges.
.Viewport {
mask-image: linear-gradient(
to bottom,
transparent 0,
black min(40px, var(--scroll-area-overflow-y-start)),
black calc(100% - min(40px, var(--scroll-area-overflow-y-end, 40px))),
transparent 100%
);
mask-repeat: no-repeat;
}
For SSR, a fallback can be used as part of the end-side var() call so the mask is visible before the overflow CSS variables hydrate.
var(--scroll-area-overflow-y-end, 40px);
When the fade is applied to <ScrollArea.Viewport> itself, the variables can be used directly. However, inheritance to children is disabled, so they must explicitly opt-in using the inherit keyword.
.Child {
--scroll-area-overflow-y-start: inherit;
--scroll-area-overflow-y-end: inherit;
}
import { ScrollArea } from 'base-ui-solid/scroll-area';
import styles from './index.module.css';
export default function ExampleScrollAreaScrollFade() {
return (
<ScrollArea.Root class={styles.ScrollArea}>
<ScrollArea.Viewport class={styles.Viewport}>
<ScrollArea.Content class={styles.Content}>
<p class={styles.Paragraph}>
Vernacular architecture is building done outside any academic tradition, and without
professional guidance. It is not a particular architectural movement or style, but
rather a broad category, encompassing a wide range and variety of building types, with
differing methods of construction, from around the world, both historical and extant and
classical and modern. Vernacular architecture constitutes 95% of the world's built
environment, as estimated in 1995 by Amos Rapoport, as measured against the small
percentage of new buildings every year designed by architects and built by engineers.
</p>
<p class={styles.Paragraph}>
This type of architecture usually serves immediate, local needs, is constrained by the
materials available in its particular region and reflects local traditions and cultural
practices. The study of vernacular architecture does not examine formally schooled
architects, but instead that of the design skills and tradition of local builders, who
were rarely given any attribution for the work. More recently, vernacular architecture
has been examined by designers and the building industry in an effort to be more energy
conscious with contemporary design and construction—part of a broader interest in
sustainable design.
</p>
</ScrollArea.Content>
</ScrollArea.Viewport>
<ScrollArea.Scrollbar class={styles.Scrollbar}>
<ScrollArea.Thumb class={styles.Thumb} />
</ScrollArea.Scrollbar>
</ScrollArea.Root>
);
}
Combining with Tabs
Use <Tabs.List>’s render prop to render <ScrollArea.Viewport> directly when the tab list itself needs the viewport overflow values for a mask fade. This keeps the mask logic on the same element that receives the scroll state.
<Tabs.Root defaultValue="overview">
<ScrollArea.Root>
<Tabs.List render={(props) => <ScrollArea.Viewport {...props} />}>
<Tabs.Tab value="overview">Overview</Tabs.Tab>
<Tabs.Indicator />
</Tabs.List>
</ScrollArea.Root>
<Tabs.Panel value="overview">...</Tabs.Panel>
</Tabs.Root>
API reference
Root
Groups all parts of the scroll area.
Renders a <div> element.
overflowEdgeThresholdUnion0
number | Partial<{ xStart: number; xEnd: number; yStart: number; yEnd: number }>classfunction—
JSX.ClassValue | ((state) => JSX.ClassValue)stylefunction—
JSX.CSSProperties | string | ((state) => JSX.CSSProperties | string | undefined)renderfunction—
keyof JSX.IntrinsicElements | Component | ((props, state) => JSX.Element)Data attributes
data-has-overflow-x-—
-data-has-overflow-y-—
-data-overflow-x-end-—
-data-overflow-x-start-—
-data-overflow-y-end-—
-data-overflow-y-start-—
-data-scrolling-—
-Attribute | Description | |
|---|---|---|
data-has-overflow-x | Present when the scroll area content is wider than the viewport. | |
data-has-overflow-y | Present when the scroll area content is taller than the viewport. | |
data-overflow-x-end | Present when there is overflow on the horizontal end side. | |
data-overflow-x-start | Present when there is overflow on the horizontal start side. | |
data-overflow-y-end | Present when there is overflow on the vertical end side. | |
data-overflow-y-start | Present when there is overflow on the vertical start side. | |
data-scrolling | Present when the user scrolls inside the scroll area. | |
CSS variables
--scroll-area-corner-heightnumber—
number--scroll-area-corner-widthnumber—
numberCSS Variable | Description | |
|---|---|---|
--scroll-area-corner-height | The scroll area’s corner height. | |
--scroll-area-corner-width | The scroll area’s corner width. | |
Root.State
type ScrollAreaRootState = {
/** Whether the scroll area is being scrolled. */
scrolling: boolean;
/** Whether horizontal overflow is present. */
hasOverflowX: boolean;
/** Whether vertical overflow is present. */
hasOverflowY: boolean;
/** Whether there is overflow on the inline start side for the horizontal axis. */
overflowXStart: boolean;
/** Whether there is overflow on the inline end side for the horizontal axis. */
overflowXEnd: boolean;
/** Whether there is overflow on the block start side. */
overflowYStart: boolean;
/** Whether there is overflow on the block end side. */
overflowYEnd: boolean;
/** Whether the scrollbar corner is hidden. */
cornerHidden: boolean;
};Viewport
The actual scrollable container of the scroll area.
Renders a <div> element.
classfunction—
JSX.ClassValue | ((state) => JSX.ClassValue)stylefunction—
JSX.CSSProperties | string | ((state) => JSX.CSSProperties | string | undefined)renderfunction—
keyof JSX.IntrinsicElements | Component | ((props, state) => JSX.Element)Data attributes
data-has-overflow-x-—
-data-has-overflow-y-—
-data-overflow-x-end-—
-data-overflow-x-start-—
-data-overflow-y-end-—
-data-overflow-y-start-—
-data-scrolling-—
-Attribute | Description | |
|---|---|---|
data-has-overflow-x | Present when the scroll area content is wider than the viewport. | |
data-has-overflow-y | Present when the scroll area content is taller than the viewport. | |
data-overflow-x-end | Present when there is overflow on the horizontal end side. | |
data-overflow-x-start | Present when there is overflow on the horizontal start side. | |
data-overflow-y-end | Present when there is overflow on the vertical end side. | |
data-overflow-y-start | Present when there is overflow on the vertical start side. | |
data-scrolling | Present when the user scrolls inside the scroll area. | |
CSS variables
--scroll-area-overflow-x-endnumber—
number--scroll-area-overflow-x-startnumber—
number--scroll-area-overflow-y-endnumber—
number--scroll-area-overflow-y-startnumber—
numberCSS Variable | Description | |
|---|---|---|
--scroll-area-overflow-x-end | The distance from the horizontal end edge in pixels. | |
--scroll-area-overflow-x-start | The distance from the horizontal start edge in pixels. | |
--scroll-area-overflow-y-end | The distance from the vertical end edge in pixels. | |
--scroll-area-overflow-y-start | The distance from the vertical start edge in pixels. | |
Viewport.State
type ScrollAreaViewportState = {
/** Whether the scroll area is being scrolled. */
scrolling: boolean;
/** Whether horizontal overflow is present. */
hasOverflowX: boolean;
/** Whether vertical overflow is present. */
hasOverflowY: boolean;
/** Whether there is overflow on the inline start side for the horizontal axis. */
overflowXStart: boolean;
/** Whether there is overflow on the inline end side for the horizontal axis. */
overflowXEnd: boolean;
/** Whether there is overflow on the block start side. */
overflowYStart: boolean;
/** Whether there is overflow on the block end side. */
overflowYEnd: boolean;
/** Whether the scrollbar corner is hidden. */
cornerHidden: boolean;
};Content
A container for the content of the scroll area.
Renders a <div> element.
classfunction—
JSX.ClassValue | ((state) => JSX.ClassValue)stylefunction—
JSX.CSSProperties | string | ((state) => JSX.CSSProperties | string | undefined)renderfunction—
keyof JSX.IntrinsicElements | Component | ((props, state) => JSX.Element)Data attributes
data-has-overflow-x-—
-data-has-overflow-y-—
-data-overflow-x-end-—
-data-overflow-x-start-—
-data-overflow-y-end-—
-data-overflow-y-start-—
-data-scrolling-—
-Attribute | Description | |
|---|---|---|
data-has-overflow-x | Present when the scroll area content is wider than the viewport. | |
data-has-overflow-y | Present when the scroll area content is taller than the viewport. | |
data-overflow-x-end | Present when there is overflow on the horizontal end side. | |
data-overflow-x-start | Present when there is overflow on the horizontal start side. | |
data-overflow-y-end | Present when there is overflow on the vertical end side. | |
data-overflow-y-start | Present when there is overflow on the vertical start side. | |
data-scrolling | Present when the user scrolls inside the scroll area. | |
Content.State
type ScrollAreaContentState = {
/** Whether the scroll area is being scrolled. */
scrolling: boolean;
/** Whether horizontal overflow is present. */
hasOverflowX: boolean;
/** Whether vertical overflow is present. */
hasOverflowY: boolean;
/** Whether there is overflow on the inline start side for the horizontal axis. */
overflowXStart: boolean;
/** Whether there is overflow on the inline end side for the horizontal axis. */
overflowXEnd: boolean;
/** Whether there is overflow on the block start side. */
overflowYStart: boolean;
/** Whether there is overflow on the block end side. */
overflowYEnd: boolean;
/** Whether the scrollbar corner is hidden. */
cornerHidden: boolean;
};Scrollbar
A vertical or horizontal scrollbar for the scroll area.
Renders a <div> element.
orientationUnion'vertical'
'vertical' | 'horizontal'classfunction—
JSX.ClassValue | ((state) => JSX.ClassValue)stylefunction—
JSX.CSSProperties | string | ((state) => JSX.CSSProperties | string | undefined)keepMountedbooleanfalse
booleanrenderfunction—
keyof JSX.IntrinsicElements | Component | ((props, state) => JSX.Element)Data attributes
data-orientationUnion—
'horizontal' | 'vertical'data-has-overflow-x-—
-data-has-overflow-y-—
-data-hovering-—
-data-overflow-x-end-—
-data-overflow-x-start-—
-data-overflow-y-end-—
-data-overflow-y-start-—
-data-scrolling-—
-Attribute | Description | |
|---|---|---|
data-orientation | Indicates the orientation of the scrollbar. | |
data-has-overflow-x | Present when the scroll area content is wider than the viewport. | |
data-has-overflow-y | Present when the scroll area content is taller than the viewport. | |
data-hovering | Present when the pointer is over the scroll area. | |
data-overflow-x-end | Present when there is overflow on the horizontal end side. | |
data-overflow-x-start | Present when there is overflow on the horizontal start side. | |
data-overflow-y-end | Present when there is overflow on the vertical end side. | |
data-overflow-y-start | Present when there is overflow on the vertical start side. | |
data-scrolling | Present when the user scrolls inside the scroll area. | |
CSS variables
--scroll-area-thumb-heightnumber—
number--scroll-area-thumb-widthnumber—
numberCSS Variable | Description | |
|---|---|---|
--scroll-area-thumb-height | The scroll area thumb’s height. | |
--scroll-area-thumb-width | The scroll area thumb’s width. | |
Scrollbar.State
type ScrollAreaScrollbarState = {
/** Whether the scroll area is being hovered. */
hovering: boolean;
/** Whether the scroll area is being scrolled. */
scrolling: boolean;
/** The orientation of the scrollbar. */
orientation: 'vertical' | 'horizontal';
/** Whether horizontal overflow is present. */
hasOverflowX: boolean;
/** Whether vertical overflow is present. */
hasOverflowY: boolean;
/** Whether there is overflow on the inline start side for the horizontal axis. */
overflowXStart: boolean;
/** Whether there is overflow on the inline end side for the horizontal axis. */
overflowXEnd: boolean;
/** Whether there is overflow on the block start side. */
overflowYStart: boolean;
/** Whether there is overflow on the block end side. */
overflowYEnd: boolean;
/** Whether the scrollbar corner is hidden. */
cornerHidden: boolean;
};Thumb
The draggable part of the scrollbar that indicates the current scroll position.
Renders a <div> element.
classfunction—
JSX.ClassValue | ((state) => JSX.ClassValue)stylefunction—
JSX.CSSProperties | string | ((state) => JSX.CSSProperties | string | undefined)renderfunction—
keyof JSX.IntrinsicElements | Component | ((props, state) => JSX.Element)Data attributes
data-orientationUnion—
'horizontal' | 'vertical'data-scrolling-—
-Attribute | Description | |
|---|---|---|
data-orientation | Indicates the orientation of the scrollbar. | |
data-scrolling | Present when the user scrolls inside the scroll area. | |
Thumb.State
type ScrollAreaThumbState = {
/** Whether the scroll area is being scrolled. */
scrolling: boolean;
/** The component orientation. */
orientation: 'horizontal' | 'vertical';
};Corner
A small rectangular area that appears at the intersection of horizontal and vertical scrollbars.
Renders a <div> element.
classfunction—
JSX.ClassValue | ((state) => JSX.ClassValue)stylefunction—
JSX.CSSProperties | string | ((state) => JSX.CSSProperties | string | undefined)renderfunction—
keyof JSX.IntrinsicElements | Component | ((props, state) => JSX.Element)Corner.State
type ScrollAreaCornerState = {};