codefast/ui

Command Palette

Search for a command to run...

Source
Layout

Scroll Area

Custom-styled scrollbar that matches your design system. Hides native OS scrollbars.

Examples

Horizontal

Use ScrollBar with orientation='horizontal' for horizontal scrolling.

Photo by Ornella Binni
Photo by Ornella Binni
Photo by Tom Byrom
Photo by Tom Byrom
Photo by Vladimir Malyavko
Photo by Vladimir Malyavko
49 lines
import { ScrollArea, ScrollAreaScrollbar } from "@codefast/ui/scroll-area";
import { Image } from "@unpic/react";

export interface Artwork {
  artist: string;
  art: string;
}

export const works: Array<Artwork> = [
  {
    artist: "Ornella Binni",
    art: "https://images.unsplash.com/photo-1465869185982-5a1a7522cbcb?auto=format&fit=crop&w=300&q=80",
  },
  {
    artist: "Tom Byrom",
    art: "https://images.unsplash.com/photo-1548516173-3cabfa4607e9?auto=format&fit=crop&w=300&q=80",
  },
  {
    artist: "Vladimir Malyavko",
    art: "https://images.unsplash.com/photo-1494337480532-3725c85fd2ab?auto=format&fit=crop&w=300&q=80",
  },
];

export function ScrollAreaHorizontalDemo() {
  return (
    <ScrollArea className="w-96 rounded-md border border-ui-border whitespace-nowrap">
      <div className="flex w-max space-x-4 p-4">
        {works.map((artwork) => (
          <figure key={artwork.artist} className="shrink-0">
            <div className="overflow-hidden rounded-md">
              <Image
                alt={`Photo by ${artwork.artist}`}
                className="aspect-[3/4] w-[150px] object-cover"
                height={400}
                layout="constrained"
                src={artwork.art}
                width={300}
              />
            </div>
            <figcaption className="pt-2 text-xs text-ui-muted">
              Photo by <span className="font-semibold text-ui-fg">{artwork.artist}</span>
            </figcaption>
          </figure>
        ))}
      </div>
      <ScrollAreaScrollbar orientation="horizontal" />
    </ScrollArea>
  );
}

RTL

Right-to-left layout support for languages such as Arabic and Hebrew.

Translations are AI-generated for demonstration and may be imperfect.

العلامات

v1.2.0-beta.50
v1.2.0-beta.49
v1.2.0-beta.48
v1.2.0-beta.47
v1.2.0-beta.46
v1.2.0-beta.45
v1.2.0-beta.44
v1.2.0-beta.43
v1.2.0-beta.42
v1.2.0-beta.41
v1.2.0-beta.40
v1.2.0-beta.39
v1.2.0-beta.38
v1.2.0-beta.37
v1.2.0-beta.36
v1.2.0-beta.35
v1.2.0-beta.34
v1.2.0-beta.33
v1.2.0-beta.32
v1.2.0-beta.31
v1.2.0-beta.30
v1.2.0-beta.29
v1.2.0-beta.28
v1.2.0-beta.27
v1.2.0-beta.26
v1.2.0-beta.25
v1.2.0-beta.24
v1.2.0-beta.23
v1.2.0-beta.22
v1.2.0-beta.21
v1.2.0-beta.20
v1.2.0-beta.19
v1.2.0-beta.18
v1.2.0-beta.17
v1.2.0-beta.16
v1.2.0-beta.15
v1.2.0-beta.14
v1.2.0-beta.13
v1.2.0-beta.12
v1.2.0-beta.11
v1.2.0-beta.10
v1.2.0-beta.9
v1.2.0-beta.8
v1.2.0-beta.7
v1.2.0-beta.6
v1.2.0-beta.5
v1.2.0-beta.4
v1.2.0-beta.3
v1.2.0-beta.2
v1.2.0-beta.1
47 lines
import { ScrollArea } from "@codefast/ui/scroll-area";
import { Separator } from "@codefast/ui/separator";
import * as React from "react";

import type { Translations } from "#/features/components-catalog/components/detail/language";
import { useTranslation } from "#/features/components-catalog/components/detail/language-context";

const tags = Array.from({ length: 50 }).map((_, i, a) => `v1.2.0-beta.${a.length - i}`);

const translations: Translations = {
  en: {
    dir: "ltr",
    values: {
      tags: "Tags",
    },
  },
  ar: {
    dir: "rtl",
    values: {
      tags: "العلامات",
    },
  },
  he: {
    dir: "rtl",
    values: {
      tags: "תגיות",
    },
  },
};

export function ScrollAreaRtl() {
  const { dir, t } = useTranslation(translations, "ar");

  return (
    <ScrollArea className="h-72 w-48 rounded-md border" dir={dir}>
      <div className="p-4">
        <h4 className="mb-4 text-sm leading-none font-medium">{t.tags}</h4>
        {tags.map((tag) => (
          <React.Fragment key={tag}>
            <div className="text-sm">{tag}</div>
            <Separator className="my-2" />
          </React.Fragment>
        ))}
      </div>
    </ScrollArea>
  );
}

Usage

The minimal import and composition — see Examples below for styled, real-world variants.

15 lines
import { ScrollArea } from "@codefast/ui/scroll-area";

export function ScrollAreaUsage() {
  return (
    <ScrollArea className="h-72 w-48 rounded-md border">
      <div className="p-4">
        {Array.from({ length: 20 }, (_, index) => (
          <p key={index} className="text-sm">
            Item {index + 1}
          </p>
        ))}
      </div>
    </ScrollArea>
  );
}

Anatomy

How the parts nest — every slot the component exposes, in composition order.

ScrollArea

Features

  • Replaces the native scrollbar visually only — real scroll and keyboard behaviour stay intact underneath.
  • Add a second ScrollAreaScrollbar orientation="horizontal" alongside the default vertical one for two-axis scrolling.
  • size (from context) controls scrollbar thickness for both orientations at once.

API reference

Props for each part of the component. All native element props are also forwarded.

ScrollArea

A viewport with a styled scrollbar that replaces the native one.

classNamestring

Set the fixed size (e.g. h-44 w-48) the content scrolls within.

ScrollAreaScrollbar

orientation"vertical" | "horizontal"

Render a horizontal scrollbar in addition to the vertical one.

Accessibility

Built to be keyboard-navigable and screen-reader friendly out of the box.

  • The viewport stays keyboard-scrollable and focusable as normal.
  • Custom scrollbars are visual; native scroll behaviour is preserved underneath.
  • Ensure content has enough contrast against the scroll surface.

Guidelines

Conventions that keep usage consistent across an app.

Do

  • Use for constrained lists, menus, and panels.
  • Give the area a clear fixed size so the scrollbar makes sense.

Don’t

  • Don’t wrap the whole page — let it scroll natively.
  • Don’t hide that content is scrollable; show a peek of the next row.

Explore further

Ready to integrate?

Follow the Getting Started guide to install @codefast/ui, or browse the full component gallery.