Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

To stop the page behind an open modal, drawer, or full-screen menu from scrolling, add a temporary scroll-lock class to the root document and remove it when the overlay closes. Keep the overlay’s own content in a separately scrollable container. Use overflow: hidden for the usual lock; choose overflow: clip when script-driven or focus-driven scrolling of the clipped content must also be prevented.

Lock the page while an overlay is open

Page scrolling is a document-level behavior, so apply the lock to both the html element and the body. A class makes the state explicit and lets your modal-opening code turn the lock on and off.

html.is-scroll-locked,
body.is-scroll-locked {
  overflow: hidden;
}
function lockPage() {
  document.documentElement.classList.add('is-scroll-locked');
  document.body.classList.add('is-scroll-locked');
}

function unlockPage() {
  document.documentElement.classList.remove('is-scroll-locked');
  document.body.classList.remove('is-scroll-locked');
}

Call lockPage() when the overlay becomes active, and unlockPage() when it closes. If the overlay can close in several ways—such as a close button, Escape key, or route change—make each close path run the same cleanup. Otherwise a forgotten unlock can leave the page stuck.

Choose between hidden and clip

overflow: hidden clips overflowing content and removes the visible scrollbar, but it does not make the element impossible to scroll. Focus moving to an off-screen element or script code using scrollTop or scrollTo() can still move the content. overflow: clip does not create a scroll container and does not support programmatic scrolling. Use it when you want a stronger clip and do not need those forms of scrolling while locked.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
html.is-scroll-locked,
body.is-scroll-locked {
  overflow: clip;
}

Do not select clip just because it sounds stricter. If focus navigation or script-controlled scrolling inside the locked element needs to work, use hidden. Whichever value you choose, make sure clipping does not hide content the user needs to reach.

Keep long modal content scrollable

Locking the page should not trap users outside a long dialog. Give the dialog a maximum height and its own scrollable overflow; overscroll-behavior: contain keeps scrolling at the panel’s boundary from chaining to a neighboring scroll area.

.dialog {
  max-block-size: 90vh;
  overflow: auto;
  overscroll-behavior: contain;
}

This pattern separates two jobs: the root document stays still, while the bounded dialog can scroll through its own content. At the dialog’s top or bottom edge, contain prevents the boundary effect from passing through to neighboring scrolling areas. Use overscroll-behavior: none if you also want to suppress the default overscroll behavior at that boundary.

Rank #2
Sale
HTML and CSS: Design and Build Websites
  • HTML CSS Design and Build Web Sites
  • Comes with secure packaging
  • It can be a gift option

Use the same idea for a drawer or full-screen menu: place the content that should scroll in a bounded element, rather than relying on the page behind it. Confirm that the element with overflow: auto is the one that actually wraps the long content; putting the rule on a wrapper with no height limit may not create the intended scroll area.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Connect the lock to a modal’s open and close events

Here is a minimal page structure. The button opens the dialog, the close button closes it, and the close event removes the lock. The dialog body remains independently scrollable if its contents exceed the available height.

<button id="open-dialog" type="button">Open details</button>

<dialog id="details-dialog" aria-labelledby="dialog-title">
  <div class="dialog-content">
    <button id="close-dialog" type="button">Close</button>
    <h2 id="dialog-title">Details</h2>
    <p>Long dialog content goes here.</p>
  </div>
</dialog>
const dialog = document.querySelector('#details-dialog');
const openButton = document.querySelector('#open-dialog');
const closeButton = document.querySelector('#close-dialog');

function lockPage() {
  document.documentElement.classList.add('is-scroll-locked');
  document.body.classList.add('is-scroll-locked');
}

function unlockPage() {
  document.documentElement.classList.remove('is-scroll-locked');
  document.body.classList.remove('is-scroll-locked');
}

openButton.addEventListener('click', () => {
  lockPage();
  dialog.showModal();
});

closeButton.addEventListener('click', () => dialog.close());
dialog.addEventListener('close', unlockPage);
dialog {
  padding: 0;
  max-block-size: 90vh;
  overflow: hidden;
}

.dialog-content {
  max-block-size: 90vh;
  overflow: auto;
  overscroll-behavior: contain;
  padding: 1.5rem;
}

The important lifecycle detail is that unlocking is tied to the dialog’s closing event, not only to one particular button. That way, any code path that closes this dialog can release the page lock. If your own modal component does not emit a close event, call the unlock function from its one shared close routine.

Preserve existing styles and handle multiple overlays

A simple class toggle is enough when the page has one overlay and the class is exclusively owned by that feature. If another part of the page may already have set the same class, blindly removing it on close can undo that other state. Likewise, do not replace an existing inline overflow policy with a guessed value such as auto.

For a single lock owner that needs to temporarily change inline styles, save the previous values and restore those exact values during cleanup:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
let previousHtmlOverflow = null;
let previousBodyOverflow = null;

function lockPageWithInlineStyles() {
  const html = document.documentElement;
  const body = document.body;

  previousHtmlOverflow = html.style.overflow;
  previousBodyOverflow = body.style.overflow;
  html.style.overflow = 'hidden';
  body.style.overflow = 'hidden';
}

function unlockPageWithInlineStyles() {
  if (previousHtmlOverflow === null || previousBodyOverflow === null) return;

  document.documentElement.style.overflow = previousHtmlOverflow;
  document.body.style.overflow = previousBodyOverflow;
  previousHtmlOverflow = null;
  previousBodyOverflow = null;
}

Prefer the class-based method for normal component state; the inline version illustrates the restoration rule when inline styles are necessary. If overlays can overlap, use one shared lock manager or reference count: acquire a lock when the first overlay opens, and release it only after the last overlay closes. This prevents closing one panel from unlocking the page while another is still open.

Rank #4
Sale
Web Design with HTML, CSS, JavaScript and jQuery Set
  • Brand: Wiley
  • Set of 2 Volumes
  • A handy two-book set that uniquely combines related technologies Highly visual format and accessible language makes these books highly effective learning tools Perfect for beginning web designers and front-end developers

Use JavaScript event cancellation only when CSS is not enough

For a component whose touch or wheel interaction still scrolls the page despite the CSS lock, a narrowly scoped listener can cancel those events while the lock is active. The listener must be non-passive to call preventDefault().

const cancelScroll = event => event.preventDefault();

function lockWithEvents() {
  document.addEventListener('wheel', cancelScroll, { passive: false });
  document.addEventListener('touchmove', cancelScroll, { passive: false });
}

function unlockWithEvents() {
  document.removeEventListener('wheel', cancelScroll);
  document.removeEventListener('touchmove', cancelScroll);
}

Attach these listeners only while the relevant overlay is open, and remove them during the same cleanup that releases the CSS lock. If the overlay itself must respond to touch scrolling, do not cancel every touch move indiscriminately: scope cancellation to the area or gesture that is escaping the overlay. Event cancellation is a fallback for a specific interaction, not a replacement for expressing the locked state in CSS.

Avoid layout shifts when the scrollbar disappears

On a layout where removing the document scrollbar changes the viewport width, opening an overlay can shift centered content or move fixed elements. Check the actual layout when the lock is applied. If the design needs stable geometry, account for the scrollbar gap in the layout rather than assuming the lock is visually neutral. The right adjustment depends on the page’s layout, so verify it with the overlay both open and closed.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Accessibility and mobile checks

  • Keyboard focus: Keep focus within an open modal, provide a visible way to close it, and restore focus appropriately after closing. With hidden, focus movement can still bring clipped content into view, which matters when deciding whether that value is suitable.
  • Reachable content: Do not use clipping to hide page content that a user must reach while the overlay is open. Make the dialog’s own content scrollable when necessary.
  • Touch behavior: Test touch scrolling on the target devices, including what happens when the panel reaches its top or bottom. Use overscroll-behavior for boundary containment and reserve event cancellation for cases CSS does not control adequately.
  • Cleanup: Verify that closing through every available path restores the page’s normal scrolling and any prior styles.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Troubleshoot a page that still scrolls

Symptom Likely cause What to check
The page behind the modal moves. The lock class was not applied to the root elements, or a close/open path left the state out of sync. Inspect whether both html and body have is-scroll-locked while the overlay is open. Confirm that every close path runs cleanup.
The modal content cannot be read to the end. The dialog has no bounded height or its content wrapper is not scrollable. Give the dialog content a maximum block size and overflow: auto; check that the content is inside that element.
Scrolling the panel’s edge moves another area. Scroll chaining is reaching a neighboring scroll container. Apply overscroll-behavior: contain to the panel that actually scrolls.
Code or keyboard focus can still move the locked page. overflow: hidden permits some programmatic and focus-driven scrolling. Decide whether that movement is needed. If not, test overflow: clip and confirm it does not make required content inaccessible.
The layout jumps when the overlay opens. Removing the scrollbar changes available viewport width. Compare open and closed geometry, then account for the scrollbar gap if stable width is required.
Touch gestures behave differently from mouse scrolling. The target device’s touch interaction is not adequately controlled by the CSS setup. Test the active overlay on the target device; use a cancelable, non-passive listener only for the interaction that needs cancellation, and remove it on close.
The page remains locked after the overlay disappears. A close path did not remove the class or restore saved values. Route all close behavior through one cleanup function or the component’s shared close event.

Or skip the browser setup

If your goal is to capture a webpage rather than implement scroll locking in your own interface, ScreenshotNeo offers a screenshot API and MCP server. It does not replace the CSS and JavaScript pattern above; it is an alternative for obtaining a screenshot without building browser-capture plumbing yourself. One GET request can return an image or PDF.

For example, this cURL request saves a WebP capture of Stripe. See the ScreenshotNeo API documentation for request options.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

ScreenshotNeo’s clean-shot options accept cookie or consent banners and remove more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each step can be turned off. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers report the page verdict and billing status. Its MCP server provides take_screenshot, get_page_info, and capture_pdf tools for AI agents using Claude, Cursor, or another MCP client. The Free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000 screenshots.

Sign up for ScreenshotNeo’s free plan to get 1,000 screenshots a month with no card.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Frequently Asked Questions

Does hiding the scrollbar stop the user from scrolling?

Not by itself. The lock needs to prevent document overflow from scrolling; removing a visible scrollbar alone is not the same as locking the page.

Should the overlay or the whole page receive the scroll-lock class?

Apply the page lock to the root document. Put independent scrolling on a bounded element inside the overlay when its content needs to scroll.

Can I use this technique for a navigation drawer?

Yes. Treat an open drawer as the active overlay state, keep any long drawer content in its own scroll container, and remove the lock when the drawer closes.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.