Why Prevent Background Scrolling
Background scrolling occurs when users scroll while a modal is open, causing the underlying page to move even though they cannot interact with it. This creates a poor user experience where users may lose their place or become disoriented about which element they're actually scrolling.
Key problems with background scrolling:
- Users lose their place on the page
- Creates cognitive dissonance between visual and interactive states
- Particularly problematic on mobile devices with touch scrolling
- Can lead to confusion about interface state
From a user-centered design perspective, preventing background scrolling maintains context and keeps users focused on the modal content they need to complete their task. When a modal opens, the interface should clearly communicate that the background is no longer accessible--background scrolling undermines this clarity and can frustrate users who expect their scroll actions to have no effect on the underlying content.
Implementing proper scroll lock behavior ensures that modal interactions remain predictable and users maintain spatial awareness throughout their session.
Modern CSS Solution: The :has() Selector
The most elegant modern approach uses the CSS :has() pseudo-class to detect when a modal is open and prevent scrolling accordingly. This method requires no JavaScript for the scroll lock behavior and works automatically with the modal's open state.
Using :has() with dialog[open]
body:has(dialog[open]) {
overflow: hidden;
}
This simple selector targets the body element when it contains a dialog with the open attribute, then prevents overflow scrolling. When the dialog closes and the open attribute is removed, the scroll lock releases automatically.
How It Works
The :has() selector checks for the existence of a descendant matching the specified criteria. When a dialog element has the open attribute, the selector matches, and overflow: hidden is applied to the body. This prevents all scrolling on the page body while the modal remains open. The browser handles the state transition automatically, so no JavaScript event handlers are needed for the scroll lock itself.
According to Frontend Masters' analysis of scroll-locked dialogs, this approach provides the cleanest, most maintainable solution for modern web applications.
Alternative: Targeting html Element
html:has(dialog[open]) {
overflow: hidden;
}
This approach can be more robust in edge cases where the <html> element has scrollable overflow set by default, ensuring consistent behavior across different page configurations. Working with our web development team ensures these CSS techniques are implemented correctly across your entire site.
Browser Support
The :has() selector has excellent browser support across all modern browsers, making it a reliable choice for production websites.
The overscroll-behavior Property
A newer CSS property offers another approach to controlling scroll behavior around modals. The overscroll-behavior property controls what happens when reaching the boundary of a scrolling area, and with Chrome 144+, it now works on non-scrollable scroll containers.
Understanding overscroll-behavior: contain
The overscroll-behavior: contain value prevents scroll chaining from happening, so that underlying elements don't scroll when you reach a boundary.
dialog {
overscroll-behavior: contain;
}
dialog::backdrop {
overflow: hidden;
overscroll-behavior: contain;
}
The key to this approach is setting overflow: hidden on the ::backdrop pseudo-element, which creates a non-scrollable scroll container. Chrome 144 introduced support for overscroll-behavior on non-scrollable scroll containers, as specified in the CSS overscroll behavior specification.
This technique complements other scroll container techniques to create a comprehensive scroll management strategy for modal interactions.
Combining Approaches
For the most robust solution across different browsers, combining the :has() selector with overscroll-behavior provides the best cross-browser compatibility while taking advantage of modern CSS features. This layered approach ensures that even if one method isn't supported in a particular browser, the other will still provide scroll locking functionality.
JavaScript Approaches
Before modern CSS solutions were available, JavaScript was the standard way to prevent background scrolling. These approaches remain useful for custom modal implementations or when supporting older browsers.
Adding a Scroll Lock Class
function openModal(modal) {
modal.setAttribute('open', '');
document.body.classList.add('scroll-locked');
}
function closeModal(modal) {
modal.removeAttribute('open');
document.body.classList.remove('scroll-locked');
}
.scroll-locked {
overflow: hidden;
}
This pattern provides fine-grained control over when scroll locking occurs and can be extended to handle additional modal states or effects. It's particularly useful when building custom modal components that need more complex state management.
Managing Scroll Position
When preventing scroll, you may want to preserve the user's scroll position so they return to the same place when the modal closes:
let scrollPosition = 0;
function openModal(modal) {
scrollPosition = window.scrollY;
document.body.style.position = 'fixed';
document.body.style.top = `-${scrollPosition}px`;
modal.setAttribute('open', '');
}
function closeModal(modal) {
modal.removeAttribute('open');
document.body.style.position = '';
document.body.style.top = '';
window.scrollTo(0, scrollPosition);
}
This approach uses fixed positioning to lock the page in place while preserving visual position, though it requires careful handling to avoid content layout shifts. Our web development services can implement these patterns with proper edge case handling.
Preventing Content Shift
When applying overflow: hidden to prevent scrolling, you may encounter content shift issues. This occurs because scrollbars take up horizontal space, and their removal causes the page content to reflow.
Using scrollbar-gutter
The scrollbar-gutter property reserves space for scrollbars at all times, so when overflow: hidden removes the scrollbars, no reflow occurs. This is particularly important for responsive web design where layout stability affects user perception of quality.
html {
scrollbar-gutter: stable;
}
With this property set, the page reserves space for scrollbars whether they are visible or not, preventing reflow when scrollbars appear or disappear. However, this leaves a blank strip where the scrollbar would be, which may affect the visual design of centered content. Understanding CSS header styles and scrollbar behavior helps create polished interfaces.
Considerations for Fixed Elements
Fixed position elements can behave unexpectedly when the scrollbar-gutter reserves space. Chrome has a known bug where scrollbar-gutter: stable doesn't affect position: fixed elements correctly. Consider testing your modal backdrop and any fixed-position elements when implementing this solution.
As noted in Frontend Masters' scroll-locked dialogs guide, the scrollbar-gutter approach requires careful testing across different browsers and scrollbar configurations to ensure consistent behavior.
Accessibility Considerations
Focus Trapping
Modal dialogs should trap focus within the modal when open, preventing users from tabbing to background content. The <dialog> element handles this automatically with showModal(), but custom modal implementations require manual focus trapping. When focus is trapped and background scrolling is prevented, users have a clear, bounded interaction space.
Screen Reader Considerations
Screen reader users should be informed when a modal opens. Use the aria-modal attribute on the modal element:
<div class="modal" aria-modal="true" role="dialog">
Modal content
</div>
The aria-modal attribute signals to assistive technologies that only the modal content is interactive. Combined with scroll locking, this creates a consistent, accessible modal experience for all users. Proper ARIA landmark roles complement these attributes for comprehensive accessibility.
Reducing Motion
Some users prefer reduced motion and may have this preference set in their system settings. Consider respecting this preference:
@media (prefers-reduced-motion: no-preference) {
body:has(dialog[open]) {
overflow: hidden;
}
}
This ensures that scroll locking respects user preferences while still providing the core functionality for those who don't have motion preferences set.
Use :has() Selector
The cleanest, most maintainable solution for modern browsers. No JavaScript required for scroll lock behavior.
Prevent Content Shift
Use scrollbar-gutter to reserve scrollbar space and prevent layout reflow when scrollbars are removed.
Test on Mobile
Mobile devices have different scroll behaviors. Test touch interactions and viewport handling.
Ensure Accessibility
Implement focus trapping, use aria-modal attribute, and respect prefers-reduced-motion preferences.
Handle Edge Cases
Test nested modals, rapid open/close sequences, and different scrollbar configurations.
Frequently Asked Questions
What is the simplest way to prevent background scrolling?
The simplest modern approach is using the CSS :has() selector: body:has(dialog[open]) { overflow: hidden; }. This requires no JavaScript and works automatically with the dialog element's open state.
Does overscroll-behavior work in all browsers?
Browser support varies. Chrome 144+ supports overscroll-behavior on non-scrollable scroll containers, which enables the dialog scroll lock technique. For broader compatibility, combine with the :has() selector approach.
Why does my content shift when the modal opens?
Content shift occurs because scrollbars take up horizontal space. When overflow:hidden removes the scrollbars, the content reflows. Use scrollbar-gutter: stable to reserve space for scrollbars and prevent this reflow.
Should I use JavaScript or CSS for scroll locking?
CSS is preferred for scroll locking when possible because it's declarative, automatically handles state changes, and requires no maintenance of JavaScript event handlers. Use JavaScript for older browser support or custom modal implementations.
CSS Header Styles
Learn about cross-browser compatible CSS techniques for styling headers and navigation elements.
Learn moreCustom Scrollbars
Implement styled scrollbars using WebKit CSS for a consistent user experience.
Learn moreReact Accessibility
Best practices for building accessible React applications with proper ARIA attributes.
Learn moreSources
- Bram.us - Use overscroll-behavior: contain to prevent a page from scrolling while a dialog is open - Technical details on the CSS solution for dialog scroll locking
- Frontend Masters Blog - Scroll-Locked Dialogs - Best practices for preventing page scroll with modern CSS
- CSS-Tricks - Prevent a page from scrolling while a dialog is open - Practical implementation guidance