Skip to content

Scroll Area

Contain scrolling content with slim scrollbars and fading edges that show more is available.

import { ScrollArea, ScrollBar } from '@youj/react'

Installation & usage

npm i @youj/react

For an app already set up with npx @youj/cli init, which installs the peers (@youj/core, @youj/motion, motion and Base UI) and wires the provider. New app? Start at Installation.

Use Scroll Area for a fixed-size region, such as a feed, build log or strip of items. Let main content scroll with the page. Avoid nested scrolling for long forms or content people must read in full.

import { ScrollArea } from '@youj/react'

export function Activity() {
  return (
    <ScrollArea aria-label="Activity" className="h-72">
      <ActivityList />
    </ScrollArea>
  )
}

Give it a height, or a width for horizontal scrolling. Scrolling is native: the wheel, touch, and the keyboard once the viewport has focus. The scrollbars overlay the content, appear while you hover or scroll, and fade away after.

Native scrollbars

ui.css gives the browser's own scrollbars the same thumb, on a transparent track. The page's bar is thin, and every other native bar takes the theme's colors, scopes included. Add yj-scrollbar to an element that scrolls natively, like a code panel or a long list, to make its bar thin and bring the thumb up to 3:1 contrast while the pointer or focus is on it.

<pre className="max-h-80 overflow-auto yj-scrollbar">{source}</pre>

It sets only scrollbar-width and scrollbar-color, so bars that macOS overlays stay overlays, and hovering changes the color, never the layout. The thumb colors are the --ui-scrollbar-thumb, --ui-scrollbar-thumb-hover and --ui-scrollbar-thumb-active tokens. Set scrollbar-color: auto on an element to hand its bars back to the platform.

Reference

Propsorientation, fade, viewportClassName (3)

Props

ScrollArea props
PropType
orientation'vertical' | 'horizontal' | 'both'

Which scrollbars to render.

default 'vertical'

'vertical'
fadeboolean

Fade the edges that have more to scroll.

default true

true
viewportClassNamestring

Classes for the viewport (the element that scrolls).

–
Other props pass through: extends ScrollAreaPrimitive.Root.Props.

ScrollBar, the bar and its thumb, is exported for custom compositions and takes Base UI's scrollbar props, such as orientation. ScrollArea already renders the bars its orientation needs.

Styling the viewportclassName styles the root, the element that has the size, border and…

Styling the viewport

className styles the root, the element that has the size, border and radius. viewportClassName reaches the element that actually scrolls, for padding that should scroll with the content or for scroll snapping.

<ScrollArea orientation="horizontal" viewportClassName="snap-x snap-mandatory" className="w-full">
  <ul className="flex w-max gap-3 p-3">{slides}</ul>
</ScrollArea>
MotionScrollbar in, on hover, scrollbar in, while scrolling, scrollbar out… · reduced motion

Motion

Motion tokens
MomentTokens
Scrollbar in, on hoveropacity
duration.slowease.standard

340ms · cubic-bezier(0.2, 0, 0, 1)

340mscubic-bezier(0.2, 0, 0, 1)
Scrollbar in, while scrollingappears almost at once
duration.instantease.standard

80ms · cubic-bezier(0.2, 0, 0, 1)

80mscubic-bezier(0.2, 0, 0, 1)
Scrollbar outafter hovering or scrolling stops
duration.slowease.standard

340ms · cubic-bezier(0.2, 0, 0, 1)

340mscubic-bezier(0.2, 0, 0, 1)
Thumbwidens from 6px to 8px and firms up on hover and press
duration.fastease.standard

140ms · cubic-bezier(0.2, 0, 0, 1)

140mscubic-bezier(0.2, 0, 0, 1)
Native thumb (yj-scrollbar)firms up while the pointer or focus is on the region
duration.fastease.standard

140ms · cubic-bezier(0.2, 0, 0, 1)

140mscubic-bezier(0.2, 0, 0, 1)
Edge fadesfollow the scroll position
no token

tied to scroll, no timing

tied to scroll, no timing

Reduced motion

Scrollbars appear and leave at once, and thumbs change at once. Scrolling and the edge fades don't change.

Reduced motion

The scrollbars appear and disappear at once, and thumbs change width and color at once. Scrolling itself, and the edge fades that follow it, are unchanged: they move only as you scroll.

AccessibilityKeyboard, ARIA, focus

Accessibility

  • The viewport is a tab stop only when it can scroll, so a short list adds nothing to the tab order. Once it has focus, the arrow keys, Page Up, Page Down, Home and End scroll it.
  • Name it with aria-label or aria-labelledby. The name goes on the viewport, which becomes a region, so screen reader users can find it and hear what it holds. Without a name it adds no landmark.
  • The focus ring is drawn by the root, outside the viewport, so the edge fades can't hide it.
  • The scrollbars are for the pointer. Keyboard and screen reader users scroll the viewport directly.