PixelScrollArea

Surface-aware scroll container with styled scrollbar, configurable visibility and dimensions.

  • Surface-aware scrollbar palette (pixel / linear)
  • Scrollbar visibility modes: auto, always, scroll, hover
  • maxHeight caps content before scrolling kicks in
  • scrollbarSize and offsetScrollbars (stable gutter) for layout stability
  • Focusable region (tabIndex 0) with focus-visible ring and dev-time a11y warning
Status
stable
Category
layout
Since
v1.9.0

API

import { PixelScrollArea } from '@pxlkit/ui-kit';
PropTypeDefaultDescription
maxHeightstring | number—Height cap before the content scrolls: pixels, or any CSS length.
variant'auto' | 'always' | 'scroll' | 'hover'—Canonical structural variant (scrollbar visibility mode).
type'auto' | 'always' | 'scroll' | 'hover'—Deprecated. Use variant instead. Retained as alias for one minor.
offsetScrollbarsbooleanfalseKeep the scrollbar's room reserved, so content never shifts.
scrollbarSizenumber—Scrollbar thickness in pixels.
surface'pixel' | 'linear'—Surface override; defaults to the nearest provider.
borderedbooleanfalseRender with surface-aware border + radius chrome. Defaults to false (no chrome).
aria-labelstring—Accessible name for the scrollable region. Required for keyboard users to understand what they've landed on. Provide either aria-label or aria-labelledby. In dev, a missing label logs a warning.
aria-labelledbystring—Id of the element that names the region, in place of aria-label.
children (required)React.ReactNode—The content that scrolls.

Also takes the native attributes and event handlers of <div> (HTMLAttributes<HTMLDivElement>).

ref points to <div>.

Accessibility

WCAG target: 2.1 AA

  • scrollable-region

Region is focusable (tabIndex 0, role="region") so keyboard users can scroll. Provide aria-label or aria-labelledby so screen-reader users know what they have landed on; a dev-time warning fires if none is set.

Keyboard

KeyActionWhen
TabMoves focus into the scroll region—
ArrowUp / ArrowDownScrolls vertically when focused—
PageUp / PageDownScrolls by a page when focused—
Home / EndJumps to start or end of the scrollable content—

Usage

import { PixelScrollArea } from '@pxlkit/ui-kit';

export function Default() {
  return (
    <PixelScrollArea aria-label="Sample scrollable region" maxHeight={160}>
      <div className="space-y-2 p-3">
        {Array.from({ length: 12 }).map((_, i) => (
          <p key={i} className="text-sm text-retro-muted">
            Scroll item {i + 1}
          </p>
        ))}
      </div>
    </PixelScrollArea>
  );
}

Examples

Default

import { PixelScrollArea } from '@pxlkit/ui-kit';

export function Default() {
  return (
    <PixelScrollArea aria-label="Sample scrollable region" maxHeight={160}>
      <div className="space-y-2 p-3">
        {Array.from({ length: 12 }).map((_, i) => (
          <p key={i} className="text-sm text-retro-muted">
            Scroll item {i + 1}
          </p>
        ))}
      </div>
    </PixelScrollArea>
  );
}

Always Visible

import { PixelScrollArea } from '@pxlkit/ui-kit';

export function AlwaysVisible() {
  return (
    <PixelScrollArea
      aria-label="Always-visible scrollbar"
      type="always"
      maxHeight={140}
      offsetScrollbars
    >
      <div className="space-y-2 p-3">
        {Array.from({ length: 10 }).map((_, i) => (
          <p key={i} className="text-sm text-retro-muted">
            Row {i + 1}
          </p>
        ))}
      </div>
    </PixelScrollArea>
  );
}

Custom Scrollbar Size

import { PixelScrollArea } from '@pxlkit/ui-kit';

export function CustomScrollbarSize() {
  return (
    <PixelScrollArea
      aria-label="Custom scrollbar size"
      type="hover"
      maxHeight={140}
      scrollbarSize={10}
    >
      <div className="space-y-2 p-3">
        {Array.from({ length: 10 }).map((_, i) => (
          <p key={i} className="text-sm text-retro-muted">
            Hover row {i + 1}
          </p>
        ))}
      </div>
    </PixelScrollArea>
  );
}

Related