October DealsAmazon USOctober deal check: compare before you payAmazon US: current deals, useful picks and tech finds.Check DealsWindows FixRecommendedWindows errors stealing your time? Find the fix fastScan stability, cleanup and performance issues.Fix NowOctober DealsAmazon USDeal season is back - check today's better picksAmazon US: current deals, useful picks and tech finds.See Picks×
Skip to content
accessibility

How to Prevent Scrolling on a Webpage with CSS and JavaScript

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

For a modal, drawer, lightbox, or full-screen menu, lock the document root while leaving the overlay’s content scrollable:

html.is-scroll-locked,
body.is-scroll-locked {
  overflow: hidden;
}

Add the class to both <html> and <body> when the overlay opens, then remove it when the overlay closes. Use overflow: clip instead of hidden when script-driven and focus-driven scrolling must be blocked too. The sections below show a complete implementation, mobile and accessibility safeguards, and a troubleshooting path.

What the scroll lock must do

A page lock has two separate jobs:

  • Stop the document itself from moving when the overlay is open.
  • Keep the overlay usable if its content is taller than the viewport.

Applying overflow only to a visible panel does not stop the underlying document. For a page-level lock, target the root document by applying a temporary state to both html and body. A class is preferable to permanently rewriting inline styles because opening and closing the component becomes an explicit state change.

CSS-first page locking

Use hidden for the usual modal case

html.is-scroll-locked,
body.is-scroll-locked {
  overflow: hidden;
}

overflow: hidden clips overflowing content and removes the visible scrollbar. It does not, however, create an absolute prohibition on movement: focus navigation, scrollTop, and scrollTo() can still bring content into view. That behavior is often desirable when keyboard focus must move to a control inside the locked region.

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

Use clip for a harder lock

html.is-scroll-locked,
body.is-scroll-locked {
  overflow: clip;
}

clip does not create a scroll container and does not support programmatic scrolling. Choose it when the requirement is to prevent both user scrolling and script-controlled movement. Test the browsers you support before making it your default, especially if your component relies on focus-driven scrolling.

Property User wheel or touch scroll Programmatic scroll and focus movement Best fit
hidden Blocked while overflow is clipped Can still move content in some cases Most dialogs and drawers
clip Blocked Not supported for the clipped element Hard locks where no movement is acceptable

Keep the modal or drawer scrollable

Do not lock every ancestor of the overlay. Give the dialog’s content a bounded block size and its own scrolling area:

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

max-block-size: 90vh keeps the panel inside the viewport in a typical horizontal writing mode. overflow: auto lets users read long content. overscroll-behavior: contain prevents scroll chaining when the panel reaches its top or bottom, so a swipe at the boundary does not start moving the page behind it. Use overscroll-behavior: none when you also want to suppress the browser’s default boundary effect.

Toggle the lock with JavaScript

The smallest reliable API adds and removes one class on each root element:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
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 modal becomes visible and unlockPage() in every close path: the close button, Escape handling, backdrop dismissal, route changes, and component unmount. Removing your class leaves any pre-existing inline overflow values and unrelated classes untouched. If another part of your application also controls overflow, coordinate the states rather than assigning auto during cleanup.

Make repeated opens and closes safe

For nested dialogs or multiple components, track ownership so one component cannot unlock a page still needed by another. A simple counter works when dialogs cannot be independently reordered:

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
let lockCount = 0;

function acquirePageLock() {
  lockCount += 1;
  if (lockCount === 1) {
    document.documentElement.classList.add('is-scroll-locked');
    document.body.classList.add('is-scroll-locked');
  }
}

function releasePageLock() {
  if (lockCount === 0) return;
  lockCount -= 1;
  if (lockCount === 0) {
    document.documentElement.classList.remove('is-scroll-locked');
    document.body.classList.remove('is-scroll-locked');
  }
}

Use a component-level manager instead if overlays can close out of order. The important rule is that cleanup must restore the state that existed before the lock.

Prevent scroll chaining at the boundary

Without containment, a user can scroll a long modal to its end and continue dragging, causing the page underneath to move. Put the containment rule on the actual scrolling element, not only on the backdrop:

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.
.dialog__content {
  max-height: min(90vh, 48rem);
  overflow-y: auto;
  overscroll-behavior-y: contain;
}

Use overscroll-behavior-y when horizontal gestures should retain their normal behavior. For a full suppression of boundary effects, change the value to none. This CSS approach is preferable to cancelling every touch event because it leaves ordinary page gestures alone when the dialog is closed.

When JavaScript event cancellation is necessary

Some touch or wheel interactions still need cancellation in a particular component. Add narrowly scoped listeners only while the lock is active:

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);
}

The passive: false option is required because the handler calls preventDefault(). Register the exact same function reference that you remove. Do not leave these listeners installed globally: otherwise normal page scrolling, embedded widgets, and other components can remain broken after the overlay closes.

Prefer CSS for the normal lock and add event cancellation only for a demonstrated device or component problem. A non-cancelable event cannot be stopped with preventDefault(), so inspect the event type and browser behavior before adding a listener.

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

Accessibility and focus behavior

Stopping movement does not make a modal accessible. While it is open:

  • Use a dialog with an accessible name, such as role="dialog", aria-modal="true", and a visible heading referenced by aria-labelledby.
  • Move focus to the dialog’s heading or first usable control.
  • Keep keyboard focus inside the open dialog; a focus trap or the native <dialog> element can provide this behavior.
  • Provide a visible close button and support Escape where appropriate.
  • Return focus to the control that opened the dialog after closing.
  • Restore the page lock even when the close operation is triggered by navigation or an exception.

With hidden, a browser may still scroll an element into view when focus moves to it. That can be useful for keyboard users. With clip, the clipped element is not a scroll container, so verify that your focus strategy does not hide the active control.

Scrollbar layout shifts

Removing the document scrollbar can increase the available layout width. Fixed headers, centered content, and elements aligned to the right edge may visibly jump when the lock is applied. Test the open and closed states at the target viewport sizes. If stable geometry is required, reserve the scrollbar gap in your layout or apply a measured compensation only while the lock class is present. Do not hard-code a universal pixel value: scrollbar widths vary by operating system, browser settings, and overlay-scrollbar configuration.

Complete modal example

This standalone example locks the page, keeps the dialog content scrollable, supports Escape, and restores focus:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
<!doctype html>
<html lang='en'>
<head>
  <meta charset='utf-8'>
  <meta name='viewport' content='width=device-width, initial-scale=1'>
  <title>Scroll lock demo</title>
  <style>
    html.is-scroll-locked,
    body.is-scroll-locked { overflow: hidden; }
    body { margin: 0; font: 16px/1.5 system-ui, sans-serif; }
    main { min-height: 180vh; padding: 2rem; }
    .backdrop {
      position: fixed; inset: 0; display: grid; place-items: center;
      padding: 1rem; background: rgb(0 0 0 / .55);
    }
    .dialog {
      width: min(42rem, 100%); max-block-size: 90vh;
      overflow: auto; overscroll-behavior: contain;
      padding: 1.5rem; background: white; border-radius: .5rem;
    }
    [hidden] { display: none; }
  </style>
</head>
<body>
  <main>
    <button id='open' type='button'>Open dialog</button>
    <p>Add enough page content to make the document scrollable.</p>
  </main>
  <div id='backdrop' class='backdrop' hidden>
    <section class='dialog' role='dialog' aria-modal='true'
             aria-labelledby='dialog-title' tabindex='-1'>
      <h2 id='dialog-title'>Scrollable dialog</h2>
      <p>Long content stays scrollable inside this panel while the page is locked.</p>
      <p>Repeat content as needed for your application.</p>
      <button id='close' type='button'>Close</button>
    </section>
  </div>
  <script>
    const openButton = document.querySelector('#open');
    const closeButton = document.querySelector('#close');
    const backdrop = document.querySelector('#backdrop');
    const dialog = document.querySelector('.dialog');
    let previousFocus;

    function openDialog() {
      previousFocus = document.activeElement;
      backdrop.hidden = false;
      document.documentElement.classList.add('is-scroll-locked');
      document.body.classList.add('is-scroll-locked');
      closeButton.focus();
    }

    function closeDialog() {
      backdrop.hidden = true;
      document.documentElement.classList.remove('is-scroll-locked');
      document.body.classList.remove('is-scroll-locked');
      if (previousFocus instanceof HTMLElement) previousFocus.focus();
    }

    openButton.addEventListener('click', openDialog);
    closeButton.addEventListener('click', closeDialog);
    document.addEventListener('keydown', event => {
      if (event.key === 'Escape' && !backdrop.hidden) closeDialog();
    });
    backdrop.addEventListener('click', event => {
      if (event.target === backdrop) closeDialog();
    });
  </script>
</body>
</html>

The sample demonstrates locking, but a production focus trap should cycle Tab and Shift+Tab within the dialog or use a well-tested accessibility primitive. If you use the native <dialog> element, verify how your target browsers handle its modal behavior and still test focus restoration.

Mobile and touch testing checklist

  • Open the overlay at the top, middle, and bottom of a long page.
  • Swipe inside a short panel, a long panel, and directly on the backdrop.
  • Test iOS Safari and Android browsers with both touch and an attached keyboard.
  • Check pull-to-refresh and edge-swipe behavior; use overscroll-behavior only where the design calls for containment.
  • Rotate the device and resize the viewport while the overlay is open.
  • Close through every route and confirm the page scrolls normally afterward.
  • Verify that screen-reader focus, keyboard focus, and visible focus indicators remain inside the modal until it closes.

Common failures and fixes

The page still scrolls

Confirm that the class is on both document.documentElement and document.body, that the class name matches the CSS exactly, and that no later rule overrides overflow. Check for an inline style or a framework utility with higher specificity.

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

The modal cannot be read

The page may be locked correctly while the panel has no bounded size. Add max-height or max-block-size and overflow: auto to the panel’s content area.

Closing leaves scrolling disabled

Look for an early return, a route transition, or an exception that skips cleanup. Centralize unlock logic and make it idempotent. If locks can nest, use ownership or a counter instead of removing the class unconditionally.

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

The page jumps sideways

The scrollbar disappeared and the viewport widened. Reserve or compensate for the scrollbar gap only during the locked state, and test on systems with overlay scrollbars as well as classic scrollbars.

Touch scrolling is inconsistent

Start with root overflow and panel overscroll-behavior. If a specific browser still scrolls the page, add temporary wheel or touchmove listeners with passive: false, then remove them during unlock. Avoid permanent document-level cancellation.

Focus moves behind the modal

Apply a focus trap or native modal dialog behavior, mark the dialog with an accessible name, and return focus to the opener. Do not rely on clipping as an accessibility mechanism.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Performance and reliability notes

Changing a class on the root is inexpensive; the visible work is usually layout recalculation caused by scrollbar removal. Keep the lock transition separate from large DOM mutations, and avoid repeatedly adding and removing listeners on every pointer event. A single open and close lifecycle is easier to audit than scattered style assignments.

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

Choose hidden when focus navigation or controlled scripts must still reach content, and clip when the locked element must not scroll programmatically. Whichever value you choose, test the exact combination of browser, operating system, input method, and overlay implementation that your users receive.

Or skip the browser setup

If you need a clean image of the page after implementing the lock, ScreenshotNeo can capture it through one request. It accepts consent banners before capture and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each cleanup step can be disabled. Bot checks, CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and the response identifies the result with X-Page-Verdict and X-Billed headers. Its MCP server provides take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients.

Use the API examples in the ScreenshotNeo documentation with your deployed URL:

cURL

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

Python

import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)

Node.js

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

ScreenshotNeo also supports full-page captures with lazy images loaded, CSS-selector element captures, dark mode, 12 device presets plus custom viewports, retina scale, PDF paper and page-range controls, HTML/CSS-to-image, custom JavaScript and CSS, pre-capture clicks, hidden selectors, selector or network-idle waits, ad and tracker blocking, custom headers, cookies, user agents and Authorization, timezone and geolocation, transparent backgrounds, resizing, configurable-TTL caching, signed image links, asynchronous jobs with signed webhooks, bulk capture for up to 100 URLs per call, a usage API, an OpenAPI specification, and compatible parameter names used by other screenshot APIs.

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

The Free plan includes 1,000 screenshots per month with no card. Paid plans are Starter $5 for 3,000, Growth $15 for 15,000, Pro $39 for 60,000, Scale $99 for 250,000, and Business $249 for 1,000,000; yearly billing provides two months free, and every feature is available on every plan. Create a free ScreenshotNeo account to start.

FAQ

Can CSS lock only vertical scrolling?

Yes. Apply overflow-y: hidden or overflow-y: clip to the locked roots while leaving horizontal overflow under a deliberate policy. Test wide tables, zoomed pages, and right-to-left layouts because horizontal movement may still expose content outside the viewport.

Does a parent page lock stop an iframe from scrolling?

No. A cross-origin iframe owns its own document and scroll state. You need cooperation from the framed page, or you must size the iframe so it does not create an inner scrolling region. For a same-origin iframe, apply the lock inside that document as well.

Why does a keyboard user sometimes move the page even when it is locked?

hidden can still allow focus-driven movement, and keyboard scrolling can target a different scroll container. Keep focus inside the modal, decide whether that behavior is acceptable, and choose clip only when suppressing programmatic movement is more important than that focus behavior.

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

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.

Leave a Reply

Your email address will not be published. Required fields are marked *

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.

Read next

Recommended PC Tool
Recommended PC Tool
PC Slower Than It Used to Be?Free scan - under a minute
Crashes, No Sound, or Screen Glitches?Free driver scan

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.