Skip to content
Yuzur1ha Open

Component referenceVersion Draft

Scroll area

ScrollArea keeps native scrolling while adding narrow, square custom tracks. Choose normal, contrast or hidden to fit the surrounding surface.

Design notes

Two-axis scrolling preserves browser keyboard behavior and Shift plus wheel for horizontal movement. Keep one scroll owner per region and respect reduced motion.

Use appearance="hidden" when rails would distract from a compact control group. Scrolling, touch and keyboard access remain available; provide a clear content cue for overflow.

Use root onWheelCapture when cancelling built-in wheel compensation over both the viewport and rails. viewportProps.onWheelCapture covers viewport content only.

Playground

Playgrounddemo.tsx

Appearance

Normal and contrast rails, or a hidden rail with native scrolling.

Normal

  1. 01Overview
  2. 02Personal info
  3. 03Security
  4. 04Devices & sessions
  5. 05Preferences
  6. 06Notifications
  7. 07Privacy
  8. 08Connections
  9. 09Appearance
  10. 10Language
  11. 11Help
  12. 12About

Contrast

  1. 01Overview
  2. 02Personal info
  3. 03Security
  4. 04Devices & sessions
  5. 05Preferences
  6. 06Notifications
  7. 07Privacy
  8. 08Connections
  9. 09Appearance
  10. 10Language
  11. 11Help
  12. 12About

Hidden

  1. 01Overview
  2. 02Personal info
  3. 03Security
  4. 04Devices & sessions
  5. 05Preferences
  6. 06Notifications
  7. 07Privacy
  8. 08Connections
  9. 09Appearance
  10. 10Language
  11. 11Help
  12. 12About

Both axes

Scroll vertically, or hold Shift and use the wheel to move horizontally. Arrow keys work when focused.

Release notes

Entry 01 — A shared visual language for independent applications.

Entry 02 — A shared visual language for independent applications.

Entry 03 — A shared visual language for independent applications.

Entry 04 — A shared visual language for independent applications.

Entry 05 — A shared visual language for independent applications.

Entry 06 — A shared visual language for independent applications.

Entry 07 — A shared visual language for independent applications.

Entry 08 — A shared visual language for independent applications.

Entry 09 — A shared visual language for independent applications.

Entry 10 — A shared visual language for independent applications.

Entry 11 — A shared visual language for independent applications.

Entry 12 — A shared visual language for independent applications.

Entry 13 — A shared visual language for independent applications.

Entry 14 — A shared visual language for independent applications.

Entry 15 — A shared visual language for independent applications.

Entry 16 — A shared visual language for independent applications.

Entry 17 — A shared visual language for independent applications.

Entry 18 — A shared visual language for independent applications.

Entry 19 — A shared visual language for independent applications.

Entry 20 — A shared visual language for independent applications.

Entry 21 — A shared visual language for independent applications.

Entry 22 — A shared visual language for independent applications.

Entry 23 — A shared visual language for independent applications.

Entry 24 — A shared visual language for independent applications.

Horizontal

Use a trackpad, Shift + wheel, or the Left and Right arrow keys.

01

Inputs

Fields, choices and validation.

02

Actions

Buttons, links and menus.

03

Feedback

Notices, status and progress.

04

Overlays

Dialogs, drawers and popovers.

05

Structure

Groups, disclosure and scrolling.

content/components/scroll-area/demo.tsx

import { ScrollArea } from "@yuzur1ha/ui"

const sections = [
  "Overview",
  "Personal info",
  "Security",
  "Devices & sessions",
  "Preferences",
  "Notifications",
  "Privacy",
  "Connections",
  "Appearance",
  "Language",
  "Help",
  "About",
]

export function ScrollAreaDemo() {
  return (
    <>
      <section className="demo-scroll-group" aria-label="Scrollbar appearances">
        <h2>Appearance</h2>
        <p className="demo-label">
          Normal and contrast rails, or a hidden rail with native scrolling.
        </p>
        <div className="demo-scroll-comparison">
          {(["normal", "contrast", "hidden"] as const).map((appearance) => (
            <div className="demo-scroll-variant" key={appearance}>
              <h3>
                {appearance === "normal"
                  ? "Normal"
                  : appearance === "contrast"
                    ? "Contrast"
                    : "Hidden"}
              </h3>
              <ScrollArea
                label={`${appearance} list`}
                appearance={appearance}
                type="always"
                className="demo-scroll-list"
              >
                <ol className="demo-scroll-items">
                  {sections.map((section, index) => (
                    <li key={section}>
                      <span>{String(index + 1).padStart(2, "0")}</span>
                      {section}
                    </li>
                  ))}
                </ol>
              </ScrollArea>
            </div>
          ))}
        </div>
      </section>
      <section
        className="demo-scroll-group"
        aria-label="Two-axis scrolling example"
      >
        <h2>Both axes</h2>
        <p className="demo-label">
          Scroll vertically, or hold Shift and use the wheel to move
          horizontally. Arrow keys work when focused.
        </p>
        <ScrollArea
          label="Release notes"
          orientation="both"
          className="demo-scroll-both"
        >
          <div className="demo-scroll-notes">
            <h3>Release notes</h3>
            {Array.from({ length: 24 }, (_, index) => (
              <p key={index}>
                Entry {String(index + 1).padStart(2, "0")} — A shared visual
                language for independent applications.
              </p>
            ))}
          </div>
        </ScrollArea>
      </section>
      <section
        className="demo-scroll-group"
        aria-label="Horizontal scrolling example"
      >
        <h2>Horizontal</h2>
        <p className="demo-label">
          Use a trackpad, Shift + wheel, or the Left and Right arrow keys.
        </p>
        <ScrollArea
          label="Component collections"
          orientation="horizontal"
          type="always"
          className="demo-scroll-horizontal"
        >
          <div className="demo-scroll-cards">
            {[
              {
                title: "Inputs",
                description: "Fields, choices and validation.",
              },
              { title: "Actions", description: "Buttons, links and menus." },
              {
                title: "Feedback",
                description: "Notices, status and progress.",
              },
              {
                title: "Overlays",
                description: "Dialogs, drawers and popovers.",
              },
              {
                title: "Structure",
                description: "Groups, disclosure and scrolling.",
              },
            ].map(({ title, description }, index) => (
              <article className="demo-scroll-card" key={title}>
                <span>{String(index + 1).padStart(2, "0")}</span>
                <h3>{title}</h3>
                <p>{description}</p>
              </article>
            ))}
          </div>
        </ScrollArea>
      </section>
    </>
  )
}

API

ScrollArea

A native scroll viewport with custom overlay rails. Root ref targets the outer container; viewportProps.ref targets the actual scrolling element. Ordinary viewport gestures and keyboard scrolling remain native.

ScrollAreaProps
PropertyTypeRequiredDefaultDescription
labelstringYes—Accessible name of the keyboard-focusable native viewport.
appearance"normal" | "contrast" | "hidden"No"contrast"Rail palette: contrast by default; normal uses a transparent track and border-colored thumb; hidden removes rails while keeping native scrolling and keyboard access.
orientation"vertical" | "horizontal" | "both"No"vertical"Axes that are scrollable and receive custom rails.
overscrollBehaviorCSSProperties["overscrollBehavior"]No"auto"Native chaining on enabled axes. Disabled axes stay auto. contain/none isolate the enabled axis; viewportProps.style can override it.
type("auto" | "always" | "scroll" | "hover")No"auto"Rail visibility: auto for overflow, always, hover, or scroll.
scrollHideDelaynumberNo600Radix hide delay in milliseconds for hover/scroll visibility modes.
dir("ltr" | "rtl")No—Text and keyboard direction: ltr or rtl.
viewportPropsOmit<ComponentPropsWithRef<"div">, "children" | "dangerouslySetInnerHTML">No—Props/ref/styles for the actual viewport. Its onWheelCapture can cancel Shift compensation over content; rails are outside this element.
onWheelCaptureWheelEventHandler<HTMLDivElement>No—Capture wheel input over the viewport and rails. Prevent default here to cancel built-in Shift/rail compensation before it runs.
childrenReactNodeNo—Content rendered inside the component.
classNamestringNo""Additional classes applied to the component’s documented root or control.
idstringNo—DOM identifier for the rendered element.
refRef<HTMLDivElement>No—Ref to the component’s documented DOM element.
contentClassNamestringNo—Class for the scrollable content wrapper inside the viewport.

No custom imperative methods. Event callbacks are listed as properties.

Scroll chaining defaults to the browser’s auto behavior: continuing at an edge can move an ancestor. Use overscrollBehavior="contain" for independent navigation surfaces. Containment applies to enabled axes only. Ordinary wheel gestures keep their native axes; Shift plus wheel moves the nearest available horizontal viewport, then its ancestor at the boundary. Sibling regions stay independent. Wheel gestures over a track use the viewport too; the tracks become interactive again after the gesture or pointer movement.