# How to Use the useScrollOverflow Hook in Astryx for Scroll Detection

> Master scroll detection with Astryx's useScrollOverflow hook. Track horizontal scroll boundaries effortlessly and enhance user experience in your React applications.

- Repository: [Meta/astryx](https://github.com/facebook/astryx)
- Tags: how-to-guide
- Published: 2026-07-02

---

**The `useScrollOverflow` hook returns a callback ref and three boolean states (`overflowStart`, `overflowEnd`, `hasOverflow`) to track horizontal scroll boundaries in Astryx components.**

The `useScrollOverflow` hook, available in the `facebook/astryx` repository, provides a declarative way to detect when scrollable content extends beyond the visible viewport. This utility is essential for building accessible carousels, overflow menus, and scrollable lists that need visual indicators or disabled navigation states. According to the Astryx source code, the hook leverages native `ResizeObserver` and scroll event listeners to measure container dimensions in real time.

## What is useScrollOverflow?

`useScrollOverflow` is a core React hook exported from `@astryxdesign/core` that monitors horizontal scroll containers. It reports whether content overflows at the start edge, end edge, or both, enabling dynamic UI adaptations.

The hook returns four key values:

- **`scrollRef`** – A callback ref function to attach to your scrollable container element
- **`overflowStart`** – Boolean indicating content exists before the current scroll position (left in LTR, right in RTL)
- **`overflowEnd`** – Boolean indicating content exists after the current scroll position (right in LTR, left in RTL)
- **`hasOverflow`** – Boolean indicating if total scroll width exceeds the visible container width

## Installation and Import

The hook is part of the Astryx core package. Import it directly from the hooks barrel file:

```typescript
import {useScrollOverflow} from '@astryxdesign/core';

```

No additional dependencies are required, as the hook utilizes native browser APIs including `ResizeObserver`.

## Implementation Details

The hook operates through three internal mechanisms as implemented in [`packages/core/src/hooks/useScrollOverflow.ts`](https://github.com/facebook/astryx/blob/main/packages/core/src/hooks/useScrollOverflow.ts).

First, it maintains an element reference using `useRef` (specifically `elRef` at line 43) to persist the DOM node across renders.

Second, it defines a `measure` function (lines 51-74) that compares `scrollWidth` against `clientWidth` and evaluates `scrollLeft` to determine boundary detection. This measurement runs on every scroll event and when the container resizes.

Third, it manages cleanup through [`sharedResizeObserver.ts`](https://github.com/facebook/astryx/blob/main/sharedResizeObserver.ts), which provides `observeResize` and `unobserveResize` utilities. The hook automatically removes listeners when the component unmounts, preventing memory leaks.

The hook updates state only when values actually change, optimizing React render cycles by avoiding redundant re-renders.

## Practical Code Examples

### Basic Horizontal List with Scroll Indicators

Attach the `scrollRef` to any container with `overflowX: auto` to detect hidden content:

```tsx
import {useScrollOverflow} from '@astryxdesign/core';

export function HorizontalList() {
  const {scrollRef, overflowStart, overflowEnd, hasOverflow} = useScrollOverflow();

  return (
    <div>
      <div
        ref={scrollRef}
        style={{
          display: 'flex',
          overflowX: 'auto',
          gap: '8px',
          padding: '4px',
          border: '1px solid #ddd',
        }}
      >
        {[...Array(12)].map((_, i) => (
          <div key={i} style={{minWidth: 120, padding: '8px', background: '#f5f5f5'}}>
            Item {i + 1}
          </div>
        ))}
      </div>
      
      {hasOverflow && (
        <div style={{marginTop: 8, fontSize: 14}}>
          {overflowStart && <span>← More content at start</span>}
          {overflowEnd && <span style={{marginLeft: 12}}>More content at end →</span>}
        </div>
      )}
    </div>
  );
}

```

The ref callback attaches seamlessly to the div, while the boolean flags drive conditional UI text.

### Carousel with Navigation Controls

Disable navigation buttons dynamically based on scroll position:

```tsx
import {useScrollOverflow} from '@astryxdesign/core';
import {ChevronLeftIcon, ChevronRightIcon} from '@astryxdesign/icons';

export function Carousel({children}: {children: React.ReactNode[]}) {
  const {scrollRef, overflowStart, overflowEnd} = useScrollOverflow();

  const scrollBy = (delta: number) => {
    const el = (scrollRef as any).current as HTMLElement | null;
    if (el) el.scrollBy({left: delta, behavior: 'smooth'});
  };

  return (
    <div style={{position: 'relative'}}>
      <button
        disabled={!overflowStart}
        onClick={() => scrollBy(-200)}
        style={{
          position: 'absolute',
          left: 0,
          top: '50%',
          transform: 'translateY(-50%)',
          opacity: overflowStart ? 1 : 0.3,
        }}
      >
        <ChevronLeftIcon />
      </button>

      <div
        ref={scrollRef}
        style={{
          overflowX: 'auto',
          display: 'flex',
          scrollSnapType: 'x mandatory',
          padding: '0 40px',
        }}
      >
        {children.map((child, i) => (
          <div key={i} style={{flex: '0 0 auto', scrollSnapAlign: 'start'}}>
            {child}
          </div>
        ))}
      </div>

      <button
        disabled={!overflowEnd}
        onClick={() => scrollBy(200)}
        style={{
          position: 'absolute',
          right: 0,
          top: '50%',
          transform: 'translateY(-50%)',
          opacity: overflowEnd ? 1 : 0.3,
        }}
      >
        <ChevronRightIcon />
      </button>
    </div>
  );
}

```

**Note:** `overflowStart` returns `true` when content is hidden at the beginning, so disable the previous button when `overflowStart` is `false`.

### RTL (Right-to-Left) Layout Support

The hook automatically adapts to document direction by basing calculations on native `scrollLeft` values:

```tsx
import {useScrollOverflow} from '@astryxdesign/core';

export function RtlScrollBox() {
  const {scrollRef, overflowStart, overflowEnd} = useScrollOverflow();

  return (
    <div
      dir="rtl"
      ref={scrollRef}
      style={{overflowX: 'auto', whiteSpace: 'nowrap', border: '1px solid #ccc'}}
    >
      <span style={{display: 'inline-block', padding: '20px', margin: '10px', background: '#eee'}}>
        Item A
      </span>
      <span style={{display: 'inline-block', padding: '20px', margin: '10px', background: '#eee'}}>
        Item B
      </span>
      <span style={{display: 'inline-block', padding: '20px', margin: '10px', background: '#eee'}}>
        Item C
      </span>
    </div>
  );
}

```

In RTL mode, `overflowStart` refers to the right edge and `overflowEnd` refers to the left edge, ensuring semantic consistency with the layout direction.

## Summary

- **`useScrollOverflow`** provides real-time detection of horizontal scroll boundaries in Astryx applications.
- The hook returns a **callback ref** (`scrollRef`) and three booleans indicating overflow state at the start, end, and overall container.
- Implementation resides in [`packages/core/src/hooks/useScrollOverflow.ts`](https://github.com/facebook/astryx/blob/main/packages/core/src/hooks/useScrollOverflow.ts) and utilizes `ResizeObserver` for efficient dimension tracking.
- The hook handles **automatic cleanup** and only triggers re-renders when state values actually change.
- **RTL layouts** are supported natively without additional configuration.

## Frequently Asked Questions

### What is the difference between `overflowStart` and `hasOverflow`?

HasOverflow indicates whether the container requires scrolling at all (content wider than viewport), while `overflowStart` specifically indicates whether hidden content exists before the current scroll position. You can have `hasOverflow: true` with `overflowStart: false` when the user has scrolled to the beginning of the content.

### Does useScrollOverflow work with vertical scrolling?

No, the hook is designed specifically for **horizontal scroll detection**. The implementation in [`useScrollOverflow.ts`](https://github.com/facebook/astryx/blob/main/useScrollOverflow.ts) measures `scrollWidth` and `clientWidth`, comparing horizontal dimensions. For vertical overflow detection, you would need a different utility or custom implementation measuring `scrollHeight` and `clientHeight`.

### How does the hook impact performance?

The hook uses `ResizeObserver` rather than polling, and updates React state only when values change from the previous measurement. According to the source code in lines 51-74 of [`useScrollOverflow.ts`](https://github.com/facebook/astryx/blob/main/useScrollOverflow.ts), state updates are conditional, preventing unnecessary re-render cycles during scroll events unless the boundary state actually changes.

### Can I use useScrollOverflow with custom scrollable components?

Yes, provided the component accepts a ref and forwards it to a scrollable DOM element. Since `useScrollOverflow` returns a **callback ref** rather than a mutable ref object, it works with any component using `React.forwardRef` or standard HTML elements. Ensure the target element has `overflow-x: auto` or `scroll` CSS applied for measurements to function correctly.