Skip to main content

Scroll Area

Scroll Area scrolls content within a set size, like a long activity log or a wide timeline. It replaces native scrollbars with themed ones that stay visible, appear on hover, or stay hidden.

Import

import { ScrollArea } from '@vortexlabs/vortex';
import type { ScrollAreaProps } from '@vortexlabs/vortex';

Other links

Features

  • Supports vertical and horizontal scrolling.
  • Shows custom scrollbars when content overflows.
  • Supports visible, hover, and hidden scrollbar modes.
  • Supports responsive width, height, maxWidth, and maxHeight.
  • Reports scroll progress and provides a ref to the scrolling element.

Vertical Scrolling

Set height to limit the viewport; taller content scrolls. Without height or maxHeight, Scroll Area fills its parent's height.

scrollableAttributes go to the focusable scrolling element, so pass its role and aria-label there. attributes and className go to the outer wrapper.

Activity entry 1
Activity entry 2
Activity entry 3
Activity entry 4
Activity entry 5
Activity entry 6
Activity entry 7
Activity entry 8
Activity entry 9
Activity entry 10
Activity entry 11
Activity entry 12
import { ScrollArea, Stack, Text } from "@vortexlabs/vortex";

export const ScrollAreaExample = () => {
  return (
    <ScrollArea
      height="12rem"
      width="100%"
      scrollbar="visible"
      scrollableAttributes={{ role: "region", "aria-label": "Activity log" }}
    >
      <Stack gap={3} padding={4}>
        {Array.from({ length: 12 }, (_, index) => (
          <Text key={index}>Activity entry {index + 1}</Text>
        ))}
      </Stack>
    </ScrollArea>
  );
};

Scrollbar Visibility

Set scrollbar to visible, hover, or hidden. The default is hover. Hidden scrollbars still allow wheel, touch, and keyboard scrolling.

visible
Update 1
Update 2
Update 3
Update 4
Update 5
Update 6
Update 7
Update 8
hover
Update 1
Update 2
Update 3
Update 4
Update 5
Update 6
Update 7
Update 8
hidden
Update 1
Update 2
Update 3
Update 4
Update 5
Update 6
Update 7
Update 8
import { ScrollArea, Stack, Text } from "@vortexlabs/vortex";

export const ScrollAreaVisibilityExample = () => {
  return (
    <Stack direction={{ xs: "column", md: "row" }} gap={4} width="100%">
      {(["visible", "hover", "hidden"] as const).map((scrollbar) => (
        <Stack key={scrollbar} gap={2} width="100%">
          <Text weight="medium">{scrollbar}</Text>
          <ScrollArea
            height="8rem"
            scrollbar={scrollbar}
            scrollableAttributes={{
              role: "region",
              "aria-label": scrollbar + " scrollbar example",
            }}
          >
            <Stack gap={3} padding={3}>
              {Array.from({ length: 8 }, (_, index) => (
                <Text key={index}>Update {index + 1}</Text>
              ))}
            </Stack>
          </ScrollArea>
        </Stack>
      ))}
    </Stack>
  );
};

Horizontal Scrolling

Limit width or maxWidth; wider content scrolls horizontally.

Design
Project milestone
Build
Project milestone
Review
Project milestone
Release
Project milestone
import { ScrollArea, Stack, Text } from "@vortexlabs/vortex";

export const ScrollAreaHorizontalExample = () => {
  return (
    <ScrollArea
      width="100%"
      maxWidth="28rem"
      height="9rem"
      scrollbar="visible"
      scrollableAttributes={{ role: "region", "aria-label": "Release milestones" }}
    >
      <Stack direction="row" width="54rem" padding={4} gap={4}>
        {["Design", "Build", "Review", "Release"].map((phase) => (
          <Stack
            key={phase}
            width="12rem"
            padding={4}
            backgroundColor="surface-subtlest"
            borderRadius="md"
          >
            <Text weight="medium">{phase}</Text>
            <Text size="body-sm-desktop">Project milestone</Text>
          </Stack>
        ))}
      </Stack>
    </ScrollArea>
  );
};

Responsive Size

Use a breakpoint object for height, maxHeight, width, or maxWidth. When you set only maxHeight, Scroll Area also uses it as the height.

Review item 1
Review item 2
Review item 3
Review item 4
Review item 5
Review item 6
Review item 7
Review item 8
Review item 9
Review item 10
import { ScrollArea, Stack, Text } from "@vortexlabs/vortex";

export const ScrollAreaResponsiveExample = () => {
  return (
    <ScrollArea
      maxHeight={{ xs: "10rem", md: "16rem" }}
      width="100%"
      scrollbar="visible"
      scrollableAttributes={{ role: "region", "aria-label": "Review checklist" }}
    >
      <Stack gap={3} padding={4}>
        {Array.from({ length: 10 }, (_, index) => (
          <Text key={index}>Review item {index + 1}</Text>
        ))}
      </Stack>
    </ScrollArea>
  );
};

Scroll Position

onScroll reports scroll progress as { x, y }, each from 0 at the start to 1 at the end. ref points to the scrolling element, so you can call native methods like scrollTo.

Scroll progress: 0%
History entry 1
History entry 2
History entry 3
History entry 4
History entry 5
History entry 6
History entry 7
History entry 8
History entry 9
History entry 10
History entry 11
History entry 12
History entry 13
History entry 14
History entry 15
"use client";

import { Button, ScrollArea, Stack, Text } from "@vortexlabs/vortex";
import { useRef, useState } from "react";

export const ScrollAreaPositionExample = () => {
  const viewportRef = useRef<HTMLDivElement>(null);
  const [position, setPosition] = useState({ x: 0, y: 0 });
  return (
    <Stack gap={3} width="100%">
      <Stack direction="row" gap={3} align="center" wrap>
        <Button
          size="sm"
          variant="secondary"
          onClick={() => viewportRef.current?.scrollTo({ top: 0 })}
        >
          Back to top
        </Button>
        <Text>Scroll progress: {Math.round(position.y * 100)}%</Text>
      </Stack>
      <ScrollArea
        ref={viewportRef}
        onScroll={setPosition}
        height="10rem"
        scrollbar="visible"
        scrollableAttributes={{ role: "region", "aria-label": "Project history" }}
      >
        <Stack gap={3} padding={4}>
          {Array.from({ length: 15 }, (_, index) => (
            <Text key={index}>History entry {index + 1}</Text>
          ))}
        </Stack>
      </ScrollArea>
    </Stack>
  );
};

Accessibility

Key
Description
Tab
Moves focus to the viewport, which is focusable by default.
Up arrowDown arrowLeft arrowRight arrowPage UpPage Down
Scrolls the focused viewport.
Provide an accessible name with aria-label in scrollableAttributes.