or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

index.md
tile.json

tessl/npm-react-remove-scroll

React component that disables scroll outside of children node while maintaining scroll functionality within.

Workspace
tessl
Visibility
Public
Created
Last updated
Describes
npmpkg:npm/react-remove-scroll@2.7.x

To install, run

npx @tessl/cli install tessl/npm-react-remove-scroll@2.7.0

index.mddocs/

React Remove Scroll

React Remove Scroll is a TypeScript React library that prevents scrolling outside of specified child elements while maintaining scroll functionality within those elements. It's designed for modals, dropdowns, and overlay components where background scrolling should be disabled.

Package Information

  • Package Name: react-remove-scroll
  • Package Type: npm
  • Language: TypeScript
  • Installation: npm install react-remove-scroll

Core Imports

import { RemoveScroll } from "react-remove-scroll";

CommonJS:

const { RemoveScroll } = require("react-remove-scroll");

Alternative imports for bundle splitting:

// UI-only component (400 bytes)
import { RemoveScroll } from "react-remove-scroll/UI";
import sidecar from "react-remove-scroll/sidecar";

<RemoveScroll sideCar={sidecar}>Content</RemoveScroll>

Basic Usage

import { RemoveScroll } from "react-remove-scroll";

function Modal({ isOpen, children }) {
  return (
    <RemoveScroll enabled={isOpen}>
      {children}
    </RemoveScroll>
  );
}

// Basic scroll prevention
<RemoveScroll>
  <div>Only this content is scrollable</div>
</RemoveScroll>

Architecture

React Remove Scroll uses a layered architecture:

  • Main Component: RemoveScroll - combines UI and side effects through a sidecar pattern
  • UI Component: Lightweight component that handles rendering and event setup
  • Sidecar Effects: Separate module containing scroll prevention logic, event listeners, and DOM manipulations
  • Type System: Comprehensive TypeScript interfaces supporting both container and forward-props patterns

Capabilities

Core Component

Main scroll prevention component with comprehensive configuration options.

/**
 * React component that prevents scrolling outside of its children while maintaining
 * scroll functionality within the children. Supports both container and forward-props modes.
 */
const RemoveScroll: React.ForwardRefExoticComponent<
  IRemoveScrollProps & React.RefAttributes<HTMLElement>
> & {
  classNames: {
    fullWidth: string;
    zeroRight: string;
  };
};

type IRemoveScrollProps = IRemoveScrollSelfProps & (ChildrenNode | ChildrenForward);

interface IRemoveScrollSelfProps {
  /** Enable/disable scroll lock behavior */
  enabled?: boolean; // default: true
  /** Control removal of document scrollbar */
  removeScrollBar?: boolean; // default: true
  /** Allow pinch-to-zoom gestures (may break scroll isolation) */
  allowPinchZoom?: boolean; // default: false
  /** Prevent setting position:relative on body */
  noRelative?: boolean; // default: false
  /** Disable event isolation outside the lock */
  noIsolation?: boolean; // default: false
  /** Use pointer-events:none for complete page isolation */
  inert?: boolean; // default: false
  /** Additional elements to include in the scroll lock - array of refs or DOM elements that should remain interactive */
  shards?: Array<React.RefObject<any> | HTMLElement>;
  /** Container element type when not using forwardProps */
  as?: string | React.ElementType; // default: 'div'
  /** Strategy for filling scrollbar gap - 'margin' adds margin, 'padding' adds padding */
  gapMode?: 'padding' | 'margin'; // default: 'margin'
  /** CSS class for container */
  className?: string;
  /** Inline styles for container */
  style?: React.CSSProperties;
  /** Forwarded ref to the container element */
  ref?: React.Ref<HTMLElement>;
}

interface ChildrenNode {
  /** Wrap children in a container element */
  forwardProps?: false;
  children: React.ReactNode;
}

interface ChildrenForward {
  /** Forward props directly to the single child element */
  forwardProps: true;
  children: React.ReactElement;
}

Usage Examples:

import { RemoveScroll } from "react-remove-scroll";

// Container mode (default)
<RemoveScroll className="modal-container">
  <div>Scrollable content</div>
</RemoveScroll>

// Forward props mode
<RemoveScroll forwardProps>
  <div className="custom-container">
    Scrollable content
  </div>
</RemoveScroll>

// Advanced configuration
<RemoveScroll
  enabled={isModalOpen}
  allowPinchZoom={true}
  removeScrollBar={true}
  shards={[buttonRef, headerRef]}
  gapMode="padding"
>
  <div>Modal content</div>
</RemoveScroll>

Static Properties

CSS class names for handling position:fixed elements when scroll lock is active.

RemoveScroll.classNames: {
  /** Class for full-width fixed elements */
  fullWidth: string;
  /** Class for right-aligned fixed elements */
  zeroRight: string;
};

Usage Examples:

import { RemoveScroll } from "react-remove-scroll";
import cx from "classnames";

// Full-width fixed header
<header className={cx("fixed-header", RemoveScroll.classNames.fullWidth)}>
  Header content
</header>

// Right-aligned fixed sidebar
<aside className={cx("fixed-sidebar", RemoveScroll.classNames.zeroRight)}>
  Sidebar content
</aside>

Bundle Splitting Components

Separate UI and sidecar components for optimized bundle loading.

/**
 * UI-only component requiring a sidecar for side effects
 * Import from: react-remove-scroll/UI
 */
const RemoveScroll: React.ForwardRefExoticComponent<
  IRemoveScrollUIProps & React.RefAttributes<HTMLElement>
>;

interface IRemoveScrollUIProps extends IRemoveScrollProps {
  /** Side effect component for scroll prevention logic */
  sideCar: React.FC<any>;
}

/**
 * Sidecar component containing scroll prevention side effects
 * Import from: react-remove-scroll/sidecar
 */
const sidecar: React.FC;

Usage Examples:

import { RemoveScroll } from "react-remove-scroll/UI";
import sidecar from "react-remove-scroll/sidecar";

// Manual sidecar usage
<RemoveScroll sideCar={sidecar}>
  <div>Content with side effects</div>
</RemoveScroll>

// Dynamic sidecar loading using use-sidecar
import { sidecar } from "use-sidecar";

const dynamicSidecar = sidecar(() => import('react-remove-scroll/sidecar'));

<RemoveScroll sideCar={dynamicSidecar}>
  <div>Content with dynamically loaded side effects</div>
</RemoveScroll>

Advanced Configuration

Shards System

Shards allow additional DOM elements to remain interactive while scroll is locked:

import { useRef } from 'react';
import { RemoveScroll } from 'react-remove-scroll';

function ModalWithShards() {
  const buttonRef = useRef<HTMLButtonElement>(null);
  const headerRef = useRef<HTMLElement>(null);

  return (
    <>
      {/* These elements remain interactive due to shards */}
      <button ref={buttonRef}>Close Modal</button>
      <header ref={headerRef}>App Header</header>
      
      <RemoveScroll shards={[buttonRef, headerRef]} enabled={isModalOpen}>
        <div>Modal content - scroll is locked everywhere else</div>
      </RemoveScroll>
    </>
  );
}

Gap Mode Strategies

// margin mode (default) - adds margin-right to body to compensate for removed scrollbar
<RemoveScroll gapMode="margin">
  <div>Content</div>
</RemoveScroll>

// padding mode - adds padding-right to body to compensate for removed scrollbar
<RemoveScroll gapMode="padding">
  <div>Content</div>
</RemoveScroll>

Performance Optimization

// For better performance on large scrollable areas - disables event isolation
<RemoveScroll noIsolation>
  <div>Large scrollable content</div>
</RemoveScroll>

// Complete isolation using pointer-events (use carefully - not portal-friendly)
<RemoveScroll inert>
  <div>Modal content</div>
</RemoveScroll>

Type Definitions

/** Scroll axis direction */
type Axis = 'v' | 'h';

/** Strategy for filling scrollbar gap */
type GapMode = 'padding' | 'margin';

/** Event handling callbacks for scroll prevention - internal use by sidecar */
interface RemoveScrollEffectCallbacks {
  /** Handles scroll events on the locked element */
  onScrollCapture(event: Event): void;
  /** Handles wheel events for mouse scroll prevention */
  onWheelCapture(event: WheelEvent): void;
  /** Handles touch move events for touch scroll prevention */
  onTouchMoveCapture(event: TouchEvent): void;
}

/** Internal props interface for side effect component */
interface IRemoveScrollEffectProps {
  /** Prevent setting position:relative on body */
  noRelative?: boolean;
  /** Disable event isolation outside the lock */
  noIsolation?: boolean;
  /** Control removal of document scrollbar */
  removeScrollBar?: boolean;
  /** Allow pinch-to-zoom gestures */
  allowPinchZoom: boolean;
  /** Use pointer-events:none for complete page isolation */
  inert?: boolean;
  /** Additional elements to include in the scroll lock */
  shards?: Array<React.RefObject<any> | HTMLElement>;
  /** Reference to the lock container element */
  lockRef: React.RefObject<HTMLElement>;
  /** Strategy for filling scrollbar gap */
  gapMode?: GapMode;
  /** Callback to set event handlers */
  setCallbacks(cb: RemoveScrollEffectCallbacks): void;
}

/** Component type with static properties */
type RemoveScrollType = React.ForwardRefExoticComponent<
  IRemoveScrollProps & React.RefAttributes<HTMLElement>
> & {
  classNames: {
    fullWidth: string;
    zeroRight: string;
  };
};

Error Handling

React Remove Scroll handles common edge cases automatically:

  • Mobile Safari: Prevents zoom on touch events while maintaining scroll
  • Shadow DOM: Traverses shadow boundaries for proper event handling
  • Portals: Compatible with React portals through event system integration
  • Multiple Locks: Supports nested scroll locks with proper cleanup
  • RTL Support: Handles right-to-left text direction for horizontal scrolling

Common issues:

  • Performance: Uses non-passive event listeners which may impact scroll performance on large scrollable areas. Consider noIsolation mode for better performance.
  • Inert Mode: The inert prop is not React portal-friendly and may cause issues in production.
  • Pinch Zoom: Enabling allowPinchZoom may break scroll isolation in some scenarios.

Browser Compatibility

  • React Versions: 16.8.0+ (requires hooks)
  • Node Versions: 10+
  • TypeScript: Full type definitions included
  • Peer Dependencies: React 16.8+ || 17.x || 18.x || 19.x