react-remove-scroll: scroll isolation for React, portals included
Removes and disables 📜in a "React" way
At a glance
- What is it?
- react-remove-scroll disables scrolling outside a chosen subtree, works through React portals because it uses the React event system, and costs about 1.7kb compressed. It also adds a non-passive scroll listener, so the isolation comes with a documented cost on large scrollable areas.
- Who is it for?
- react-remove-scroll fits a React application that opens a modal-like panel, a drawer or a sheet and needs the page behind it frozen while portals and nested scroll areas keep working, and it does not fit a project that cannot tolerate a non-passive scroll listener, since the documentation says the synchronous handler may affect scrolling performance.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 7 days ago.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Scroll isolation that follows React portals
The problem react-remove-scroll solves is narrow and real: you have a dialog, a drawer or a bottom sheet, and the page behind it must not scroll while the panel is open. Many libraries solve this by setting overflow hidden on the body, which breaks in the presence of React portals because a portal renders outside the DOM subtree of the component that created it, so a portal inside your modal ends up outside the container whose overflow you locked. This library takes the opposite approach and uses the React event system, so events are captured logically through the React tree rather than through the DOM tree, and a portal that is a React child of the locked subtree stays scrollable. The README also lists support for nested scrollable elements, mouse and touch devices, and both vertical and horizontal scrolling, and the repository topics are simply react and scroll, which tells you the scope has not drifted.
Installing from npm and wrapping the scrollable content
The package is published on npm as react-remove-scroll, and the README does not give an install command, so the package name is what you add to your dependency list. Usage is then a single wrapper component.
import {RemoveScroll} from 'react-remove-scroll';
<RemoveScroll>
Only this content would be scrollable
</RemoveScroll>Everything outside the children stops scrolling, and the props are what you tune. The enabled prop activates or deactivates the behaviour without unmounting the component, which is how you would open and close a panel without losing its state. removeScrollBar controls whether the scroll bar is removed, and the documentation notes you can set it to false if you prefer to keep the bar, since wheel and touch scrolling is still disabled either way. noRelative prevents the library from setting position: relative on the body, and noIsolation disables the outer event capturing, which the README describes as React friendly and unlikely to be a problem unless you are using some form of shadow box. The forwardProps and className props control what happens to the internal div.
The internal div can be kept, restyled, or removed
By default RemoveScroll creates a div to handle and capture events, and the documentation gives you two ways to deal with it. You can give it a className and style it yourself.
<RemoveScroll className="scroll">
Only this content would be scrollable
</RemoveScroll>Or you can forward the props to your own element and let the library inject them.
<RemoveScroll forwardProps>
<div className="scroll"> //RemoveScroll will inject props to this div
Only this content would be scrollable
</div>
</RemoveScroll>The recommendation in the documentation is to pick the first one if you do not need a second element, which keeps the DOM shallower. The forwardProps route exists because the injected props are what make the element behave as the scroll container, and if you need your own markup, for example for a flex layout, you can take them instead of adding a wrapper. Two special class names exist for position: fixed elements, RemoveScroll.classNames.fullWidth for width: 100% and RemoveScroll.classNames.zeroRight for right: 0, and those are documented in the react-remove-scroll-bar project.
The inert prop is the one that can break a production app
The inert prop is flagged in the documentation as dangerous. It disables events for the rest of the page completely using pointer-events, except for the Lock and its shards, and the documentation says React portals are not friendly with it and that it might lead to production issues, recommending it only for rare cases where you have to disable scrollbars somewhere on the page outside the body, Lock and shards. The section titled Over isolation explains the failure mode in the author's own terms: you disable scroll on the body, you suppress all scroll and wheel events, and you ghost the rest of the page with inert, and then only something inside Lock exists for the browser, which might be less than you expected. The advice that follows is to be careful, to remember the shard option, and to remember that inert is not portal friendly. The practical rule is that inert is a last resort, not a default.
A non-passive listener is the price of the isolation
The performance section is the most useful paragraph in the documentation and it is easy to skip. To do its job, the library sets up a non-passive event listener, and Chrome dev tools would complain about it as a performance no-op. The reason given is that the library has to use a synchronous scroll and touch handler, and that it may affect scrolling performance. The mitigation offered is concrete: consider using noIsolation mode if you have large scrollable areas. That is a genuine trade-off rather than a warning you can ignore, because the whole point of the library is to intercept scroll events before the browser handles them, and any scheme that reacts synchronously to scroll has a cost. A developer with a heavy table inside a locked container should measure before shipping.
A sidecar entry point splits the size cost in two
The size story is unusually well documented. The full build is 1.7kb after compression, excluding tslib, and the documentation breaks that down into a UI part of 400 bytes containing only the visual elements and a sidecar part of 1.5kb holding the side effects. That split lets you load the side effects only when they are actually needed.
import {sidecar} from "react-remove-scroll";
import {RemoveScroll} from 'react-remove-scroll/UI';
const sidecar = sidecar(() => import('react-remove-scroll/sidecar'));
<RemoveScroll sideCar={sidecar}>
Will load logic from a sidecar when needed
</RemoveScroll>The package layout backs this up, with separate UI/ and sidecar/ directories at the top level and both listed in the files field of package.json alongside dist. A build script runs size-limit and writes the result to .size.json, and the repository carries a .size-limit config, so the size claims are checked in CI rather than asserted in prose. The documentation also suggests setting -webkit-overflow-scrolling: touch on a document level for a proper mobile experience.
React version support, release cadence and a MIT licence
Version support is split across two lines. The documentation states that v1 supports React 15 and 16, while v2 requires 16.8.0 and later because it uses hooks. The peer dependency range in package.json is react ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc, with @types/react marked optional, and the engines field asks for node >=10. The release history shows v2.2.0 in 2019, v2.5.5 in 2022 and v2.5.7 with shadow dom support in 2023, while package.json reads version 2.7.2, which means the tagged releases and the published version are not in step, so a consumer pinning to a GitHub release gets older code than npm. The licence is MIT, which places no restriction on commercial use and requires only that the notice travels with the code. The library is a refactoring of react-scroll-locky, made smaller and more portal friendly, and it is the building block under react-focus-on.
Editorial conclusion
react-remove-scroll fits a React application that opens a modal-like panel, a drawer or a sheet and needs the page behind it frozen while portals and nested scroll areas keep working, and it does not fit a project that cannot tolerate a non-passive scroll listener, since the documentation says the synchronous handler may affect scrolling performance. Before you adopt it, read the performance section and decide whether noIsolation is acceptable for your large scroll areas, and note that the peer dependency range lists react ^16.8.0 through ^19.0.0, so React 15 users are on the older v1 line and the package version in package.json is 2.7.2 while the newest tagged GitHub release is v2.5.7 from 2023.
Frequently asked questions
How do I use react-remove-scroll to lock the page behind a panel?
Wrap the scrollable content in the RemoveScroll component imported from react-remove-scroll. Everything outside the children stops scrolling, and the enabled prop lets you turn the behaviour on and off without removing the component from the tree.
Does react-remove-scroll work with React portals?
Yes. The documentation states that it supports react-portals because it uses the React event system, capturing events through the React tree rather than the DOM tree, so a portal rendered inside the locked subtree stays scrollable.
What React versions does react-remove-scroll support?
Version 1 supports React 15 and 16, and version 2 requires 16.8.0 or later because it uses hooks. The peer dependency range lists react ^16.8.0, ^17.0.0, ^18.0.0, ^19.0.0 and ^19.0.0-rc.
Is there a performance cost to react-remove-scroll?
Yes. The library sets up a non-passive event listener, which Chrome dev tools flags as a performance no-op, because it needs a synchronous scroll and touch handler, and this may affect scrolling performance. The documentation suggests using noIsolation mode if you have large scrollable areas.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/thekashey-react-remove-scroll)Was this useful?