Scroll Area
Scroll areas scroll their content inside a fixed box and draw a slim scrollbar over it.
import { ScrollArea } from '@aksornproject/aksara-ui'Vertical scrolling
ScrollArea is a namespace of six parts. Root is the box, and its size sets how much content shows. Viewport fills the Root and scrolls. Content wraps what scrolls. Scrollbar holds a Thumb.
The Scrollbar shows only while the content is taller than the Root. The thumb length is the share of the content in view, so a long list gets a short thumb. The track turns light grey while the pointer is over it or drags the thumb.
<ScrollArea.Root className="h-[240px] w-full max-w-[320px] rounded-radius-radius-8 border border-color-border">
<ScrollArea.Viewport role="region" aria-label="Lessons">
<ScrollArea.Content>
<ul className="flex flex-col gap-spacing-8 p-spacing-16">
{LESSONS.map((lesson) => (
<li key={lesson}>{lesson}</li>
))}
</ul>
</ScrollArea.Content>
</ScrollArea.Viewport>
<ScrollArea.Scrollbar>
<ScrollArea.Thumb />
</ScrollArea.Scrollbar>
</ScrollArea.Root>Horizontal scrolling
orientation="horizontal" on the Scrollbar draws it along the bottom edge. A wheel over the track scrolls sideways.
<ScrollArea.Root className="w-full max-w-[320px] rounded-radius-radius-8 border border-color-border">
<ScrollArea.Viewport role="region" aria-label="Units">
<ScrollArea.Content>
<ul className="flex w-max gap-spacing-12 p-spacing-16 pb-spacing-20">
{UNITS.map((unit) => (
<li
key={unit}
className="flex size-[96px] items-center justify-center rounded-radius-radius-8 bg-color-background-muted"
>
{unit}
</li>
))}
</ul>
</ScrollArea.Content>
</ScrollArea.Viewport>
<ScrollArea.Scrollbar orientation="horizontal">
<ScrollArea.Thumb />
</ScrollArea.Scrollbar>
</ScrollArea.Root>Both directions
Add a Scrollbar for each direction and a Corner. The Corner fills the square where the two tracks meet, so they never overlap.
<ScrollArea.Root className="h-[240px] w-full max-w-[320px] rounded-radius-radius-8 border border-color-border">
<ScrollArea.Viewport role="region" aria-label="Timetable">
<ScrollArea.Content>
<table>
<tbody>
{CLASSES.map((name) => (
<tr key={name}>
{PERIODS.map((period) => (
<td
key={period}
className="px-spacing-12 py-spacing-4 whitespace-nowrap"
>
{`${name}, ${period}`}
</td>
))}
</tr>
))}
</tbody>
</table>
</ScrollArea.Content>
</ScrollArea.Viewport>
<ScrollArea.Scrollbar>
<ScrollArea.Thumb />
</ScrollArea.Scrollbar>
<ScrollArea.Scrollbar orientation="horizontal">
<ScrollArea.Thumb />
</ScrollArea.Scrollbar>
<ScrollArea.Corner />
</ScrollArea.Root>Styling
Each part merges className last, so your classes win. Give the Root a height or a width, because the Viewport only scrolls when the Root is smaller than the content. The scrollbars float over the content, so leave room at the edge when text runs under the track.
Accessibility
- The scrollbars are hidden from assistive technology and never take focus. Keyboard and screen reader users scroll the Viewport.
- The Viewport becomes a tab stop while its content overflows. Give it
role="region"and anaria-labelthat names the content, such as "Lessons", so a screen reader announces what the focused box holds. - After a Tab, the Viewport scrolls with the browser scrolling keys, such as Page Down and End.
- The focus ring draws around the Viewport while it has keyboard focus.
API
ScrollArea.Root
Prop
Type
ScrollArea.Viewport
Prop
Type
ScrollArea.Content
Prop
Type
ScrollArea.Scrollbar
Prop
Type
ScrollArea.Thumb
Prop
Type
ScrollArea.Corner
Prop
Type