Bloom Logo

Scroll Area

Augments native scroll functionality with custom cross-browser styled scrollbars built on Radix UI primitives.

importimport { ScrollArea } from "@/components/ui/scrollArea/scrollArea";
$npx @bloomui-react/cli add scrollArea
scrollArea.tsx
"use client";

import { Icon } from "@iconify/react";
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area";
import * as React from "react";
import { cn } from "@/lib/utils";

export interface ScrollAreaProps
  extends React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root> {
  orientation?: "vertical" | "horizontal" | "both";
  showScrollButtons?: boolean;
  showProgressBar?: boolean;
}

const ScrollBar = React.forwardRef<
  React.ComponentRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>,
  React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>
>(({ className, orientation = "vertical", ...props }, ref) => (
  <ScrollAreaPrimitive.ScrollAreaScrollbar
    ref={ref}
    orientation={orientation}
    className={cn(
      "flex touch-none select-none transition-colors p-0.5 bg-transparent",
      orientation === "vertical" &&
        "h-full w-2.5 border-l border-l-transparent",
      orientation === "horizontal" &&
        "h-2.5 flex-col border-t border-t-transparent",
      className,
    )}
    {...props}
  >
    <ScrollAreaPrimitive.ScrollAreaThumb className="relative flex-1 rounded-full bg-zinc-300 dark:bg-zinc-700 hover:bg-zinc-400 dark:hover:bg-zinc-600 transition-colors" />
  </ScrollAreaPrimitive.ScrollAreaScrollbar>
));
ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName;

const ScrollArea = React.forwardRef<
  React.ComponentRef<typeof ScrollAreaPrimitive.Root>,
  ScrollAreaProps
>(
  (
    {
      className,
      children,
      orientation = "vertical",
      showScrollButtons = false,
      showProgressBar = false,
      ...props
    },
    ref,
  ) => {
    const viewportRef = React.useRef<HTMLDivElement>(null);
    const [scrollProgress, setScrollProgress] = React.useState(0);
    const [canScrollUp, setCanScrollUp] = React.useState(false);
    const [canScrollDown, setCanScrollDown] = React.useState(false);

    const handleScroll = React.useCallback(() => {
      const el = viewportRef.current;
      if (!el) return;
      const { scrollTop, scrollHeight, clientHeight } = el;
      const total = scrollHeight - clientHeight;
      setScrollProgress(total > 0 ? (scrollTop / total) * 100 : 0);
      setCanScrollUp(scrollTop > 20);
      setCanScrollDown(scrollTop + clientHeight < scrollHeight - 20);
    }, []);

    React.useEffect(() => {
      const el = viewportRef.current;
      if (!el) return;
      handleScroll();
      el.addEventListener("scroll", handleScroll);
      return () => el.removeEventListener("scroll", handleScroll);
    }, [handleScroll]);

    const scrollToTop = () => {
      viewportRef.current?.scrollTo({ top: 0, behavior: "smooth" });
    };

    const scrollToBottom = () => {
      if (viewportRef.current) {
        viewportRef.current.scrollTo({
          top: viewportRef.current.scrollHeight,
          behavior: "smooth",
        });
      }
    };

    return (
      <ScrollAreaPrimitive.Root
        ref={ref}
        className={cn("relative overflow-hidden group/scrollarea", className)}
        {...props}
      >
        {showProgressBar && (
          <div className="absolute top-0 left-0 right-0 h-1 bg-zinc-100 dark:bg-zinc-800 z-30">
            <div
              className="h-full bg-sky-500 transition-all duration-150"
              style={{ width: `${scrollProgress}%` }}
            />
          </div>
        )}

        <ScrollAreaPrimitive.Viewport
          ref={viewportRef}
          className="size-full rounded-[inherit] [-webkit-overflow-scrolling:touch]"
        >
          {children}
        </ScrollAreaPrimitive.Viewport>

        {showScrollButtons && (
          <div className="absolute bottom-3 right-4 z-30 flex flex-col gap-1.5 opacity-0 group-hover/scrollarea:opacity-100 transition-opacity duration-200">
            {canScrollUp && (
              <button
                type="button"
                onClick={scrollToTop}
                aria-label="Scroll to top"
                className="flex items-center justify-center size-8 rounded-full bg-zinc-900/90 dark:bg-zinc-100/90 text-white dark:text-zinc-900 shadow-lg hover:scale-110 active:scale-95 transition-all"
              >
                <Icon icon="hugeicons:arrow-up-01" className="size-4" />
              </button>
            )}
            {canScrollDown && (
              <button
                type="button"
                onClick={scrollToBottom}
                aria-label="Scroll to bottom"
                className="flex items-center justify-center size-8 rounded-full bg-zinc-900/90 dark:bg-zinc-100/90 text-white dark:text-zinc-900 shadow-lg hover:scale-110 active:scale-95 transition-all"
              >
                <Icon icon="hugeicons:arrow-down-01" className="size-4" />
              </button>
            )}
          </div>
        )}

        {(orientation === "vertical" || orientation === "both") && (
          <ScrollBar orientation="vertical" />
        )}
        {(orientation === "horizontal" || orientation === "both") && (
          <ScrollBar orientation="horizontal" />
        )}
        <ScrollAreaPrimitive.Corner />
      </ScrollAreaPrimitive.Root>
    );
  },
);
ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName;

export { ScrollArea, ScrollBar };

Default

Vertical scrollable list container.

Changelog Items

Release v1.2.1 — Updated UI design system component tokens.
Release v1.2.2 — Updated UI design system component tokens.
Release v1.2.3 — Updated UI design system component tokens.
Release v1.2.4 — Updated UI design system component tokens.
Release v1.2.5 — Updated UI design system component tokens.
Release v1.2.6 — Updated UI design system component tokens.
Release v1.2.7 — Updated UI design system component tokens.
Release v1.2.8 — Updated UI design system component tokens.
Release v1.2.9 — Updated UI design system component tokens.
Release v1.2.10 — Updated UI design system component tokens.
Release v1.2.11 — Updated UI design system component tokens.
Release v1.2.12 — Updated UI design system component tokens.
Release v1.2.13 — Updated UI design system component tokens.
Release v1.2.14 — Updated UI design system component tokens.

Horizontal Scrolling

Horizontal scroll container using orientation=horizontal.

orientation: vertical | horizontal | both
v1.0.0
React 19
Next.js 16
Tailwind CSS v4
Radix UI
TypeScript
Iconify
Design System
UI Components
Accessibility
Dark Mode

Floating Action Scroll Buttons

Enable floating scroll-to-top and scroll-to-bottom action buttons on hover with showScrollButtons.

showScrollButtons: boolean

Long Document

Item 1: Hover over this container to see the floating scroll-to-top and scroll-to-bottom action buttons.

Item 2: Hover over this container to see the floating scroll-to-top and scroll-to-bottom action buttons.

Item 3: Hover over this container to see the floating scroll-to-top and scroll-to-bottom action buttons.

Item 4: Hover over this container to see the floating scroll-to-top and scroll-to-bottom action buttons.

Item 5: Hover over this container to see the floating scroll-to-top and scroll-to-bottom action buttons.

Item 6: Hover over this container to see the floating scroll-to-top and scroll-to-bottom action buttons.

Item 7: Hover over this container to see the floating scroll-to-top and scroll-to-bottom action buttons.

Item 8: Hover over this container to see the floating scroll-to-top and scroll-to-bottom action buttons.

Item 9: Hover over this container to see the floating scroll-to-top and scroll-to-bottom action buttons.

Item 10: Hover over this container to see the floating scroll-to-top and scroll-to-bottom action buttons.

Item 11: Hover over this container to see the floating scroll-to-top and scroll-to-bottom action buttons.

Item 12: Hover over this container to see the floating scroll-to-top and scroll-to-bottom action buttons.

Item 13: Hover over this container to see the floating scroll-to-top and scroll-to-bottom action buttons.

Item 14: Hover over this container to see the floating scroll-to-top and scroll-to-bottom action buttons.

Item 15: Hover over this container to see the floating scroll-to-top and scroll-to-bottom action buttons.

Item 16: Hover over this container to see the floating scroll-to-top and scroll-to-bottom action buttons.

Item 17: Hover over this container to see the floating scroll-to-top and scroll-to-bottom action buttons.

Item 18: Hover over this container to see the floating scroll-to-top and scroll-to-bottom action buttons.

Item 19: Hover over this container to see the floating scroll-to-top and scroll-to-bottom action buttons.

Item 20: Hover over this container to see the floating scroll-to-top and scroll-to-bottom action buttons.

Scroll Progress Bar

Display a top progress indicator bar showing current scroll percentage with showProgressBar.

showProgressBar: boolean

Reading Progress

Paragraph 1: Scroll down to watch the top progress bar dynamically fill.

Paragraph 2: Scroll down to watch the top progress bar dynamically fill.

Paragraph 3: Scroll down to watch the top progress bar dynamically fill.

Paragraph 4: Scroll down to watch the top progress bar dynamically fill.

Paragraph 5: Scroll down to watch the top progress bar dynamically fill.

Paragraph 6: Scroll down to watch the top progress bar dynamically fill.

Paragraph 7: Scroll down to watch the top progress bar dynamically fill.

Paragraph 8: Scroll down to watch the top progress bar dynamically fill.

Paragraph 9: Scroll down to watch the top progress bar dynamically fill.

Paragraph 10: Scroll down to watch the top progress bar dynamically fill.

Paragraph 11: Scroll down to watch the top progress bar dynamically fill.

Paragraph 12: Scroll down to watch the top progress bar dynamically fill.

Paragraph 13: Scroll down to watch the top progress bar dynamically fill.

Paragraph 14: Scroll down to watch the top progress bar dynamically fill.

Paragraph 15: Scroll down to watch the top progress bar dynamically fill.

Paragraph 16: Scroll down to watch the top progress bar dynamically fill.

Paragraph 17: Scroll down to watch the top progress bar dynamically fill.

Paragraph 18: Scroll down to watch the top progress bar dynamically fill.

Paragraph 19: Scroll down to watch the top progress bar dynamically fill.

Paragraph 20: Scroll down to watch the top progress bar dynamically fill.

API Reference

Props — ScrollArea

Supported properties for ScrollArea.

PropTypeDefaultDescription
showScrollButtonsbooleanfalseDisplays floating scroll-to-top / scroll-to-bottom action buttons on hover.
showProgressBarbooleanfalseDisplays top scroll progress indicator line.
orientation'vertical' | 'horizontal' | 'both''vertical'Scroll direction layout and scrollbar visibility.