HorizontalScroller

Render items in a horizontally scrolling row with an optional title, progress ring and edge fade.

HorizontalScroller is a client component ("use client") that renders items in an overflowX="scroll" flex row. Use it for card rows and tag lists that should scroll sideways instead of wrapping. It uses motion/react to track horizontal scroll progress for a circular progress indicator and an animated gradient mask, and ResizeObserver to detect overflow.

Release history

v1.8

January

v1.9

February

v2.0

March

v2.1

June

v2.2

September

v2.3

November

Import#

typescript
import { HorizontalScroller } from "@reactberry/system/blocks";

Usage#

typescript
import { HorizontalScroller } from "@reactberry/system/blocks";
import { Box, Text } from "@reactberry/system/elements";

const releases = [
  { id: "r1", name: "v2.0", date: "March" },
  { id: "r2", name: "v2.1", date: "June" },
  { id: "r3", name: "v2.2", date: "September" },
];

export default function ReleaseRow() {
  return (
    <HorizontalScroller
      title="Recent releases"
      items={releases}
      gap="m"
      renderItem={(release) => (
        <Box minWidth="16rem" skin="surface" shape="rounded" p="m">
          <Text fontWeight="600">{release.name}</Text>
          <Text as="p" color="secondary">{release.date}</Text>
        </Box>
      )}
    />
  );
}

Examples#

Plain row#

Turn off progressIndicator and withMask, and leave out title, for a bare scrolling row.

Design
Accessibility
Performance
Testing
Documentation
Tooling
Theming
Releases

API#

HorizontalScroller#

PropTypeDefaultDescription
items*
any[]
—

Data to render.

renderItem*
(item: any, index: number) => ReactNode
—

Renders each item. Each result is wrapped in a Box keyed by its index.

gap
string | number
small

Gap between items.

progressIndicator
boolean
true

Shows a 2rem circular ring that fills with scroll progress.

withMask
boolean
true

Fades the edges with a gradient mask while the content overflows.

title
string
—

Rendered as an h3 above the row.

No other props are accepted; nothing is spread onto the root.

Accessibility#

  • The progress ring has no accessible label.
  • The scroll row is not keyboard-focusable by default. Ensure items contain focusable elements or add tabIndex inside renderItem output where needed.

Notes#

  • Each rendered item is wrapped in a Box keyed by its index, so give items a fixed or minimum width to stop them shrinking.
  • The mask fades the right edge at the start, the left edge at the end, and both edges in between. It is only applied when the content is wider than the container, re-checked on resize and when items changes.
  • The progress ring is drawn in the brand colour with a fixed light translucent track, which may have low contrast on light backgrounds.
  • The row always uses overflow-x: scroll, so some platforms show a scrollbar even when content fits.
  • The outer wrapper cannot be styled through props; wrap the component in a Box for spacing.