Fix Playwright Screenshots That Include a Web Page’s Scrollbars
Hide scrollbars in a Playwright screenshot with temporary CSS while keeping the page scrollable. Learn what fullPage does, how to handle nested scrollers, and what to check when output varies.
To hide scrollbars in a Playwright screenshot without disabling scrolling, pass a temporary CSS stylesheet with the style option to page.screenshot(). Use scrollbar-width: none for standardized scrollbar hiding and a ::-webkit-scrollbar rule as a practical companion for browsers that support it. This affects the capture, not your application’s permanent stylesheet, and keeps scrollable elements scrollable.
const screenshotStyle = `
*, *::before, *::after {
scrollbar-width: none !important;
}
*::-webkit-scrollbar {
display: none !important;
}
`;
await page.screenshot({
path: 'page.png',
fullPage: true,
style: screenshotStyle,
});
The screenshot style option was added in Playwright v1.41. Playwright documents that its injected stylesheet pierces Shadow DOM and applies to inner frames. Playwright screenshot API
1. Hide scrollbars for one screenshot
Use the code above after navigating to the page and waiting for the state you want to capture. The CSS is applied for that screenshot call, so the live page’s normal stylesheet does not need to change.
const { chromium } = require('playwright');
(async () => {
const browser = await chromium.launch();
const page = await browser.newPage({ viewport: { width: 1280, height: 800 } });
await page.goto('https://example.com', { waitUntil: 'load' });
const screenshotStyle = `
*, *::before, *::after {
scrollbar-width: none !important;
}
*::-webkit-scrollbar {
display: none !important;
}
`;
await page.screenshot({
path: 'page.png',
fullPage: true,
style: screenshotStyle,
});
await browser.close();
})();
Install Playwright in the project and run this JavaScript file with Node.js. If your project uses a different supported language binding, use that binding’s screenshot API and its equivalent style option where available. The CSS itself is ordinary page CSS.
Why this CSS keeps scrolling available
scrollbar-width: none hides the scrollbar without disabling the element’s scrolling. It is not inherited, so the universal selector in the example applies it to each element, including nested scroll containers. The WebKit pseudo-element rule is non-standard; use it as a compatibility companion and check the actual browser engines in your project. MDN: scrollbar-width · MDN: ::-webkit-scrollbar
2. Choose viewport or full-page capture
Set fullPage: true when the output should include the full scrollable page. Leave it out, or set it to false, to capture the current viewport. The option controls capture extent; it does not promise that scrollbars will be hidden or visible. Playwright screenshot API
// Current viewport
await page.screenshot({
path: 'viewport.png',
style: screenshotStyle,
});
// Entire scrollable page
await page.screenshot({
path: 'full-page.png',
fullPage: true,
style: screenshotStyle,
});
For an element that scrolls inside the page, the same CSS can hide its scrollbar while the element remains scrollable. A full-page screenshot concerns the page’s scrollable extent; it does not mean every nested panel is expanded to show all of its contents. If you need a particular panel’s content in the image, arrange that panel’s scroll position or layout before capture and verify the result.
3. Keep the capture style scoped and accessible
Hiding scrollbars can make scrolling harder to discover or use for people who rely on visible scrolling controls. Keep this rule in the screenshot call when it is only needed for generated images, snapshots, or visual regression output. Avoid putting it in the site’s normal stylesheet unless the product design has another clear way to scroll and the accessibility impact has been considered. MDN: scrollbar-width
Do not substitute overflow: hidden when the goal is only to remove the scrollbar. That changes overflow behavior and can prevent users or page code from scrolling the affected content.
4. Diagnose differences between screenshots
If the scrollbar is still present, or the output differs across runs, compare these factors using the same page state and capture settings:
- Playwright version: the screenshot
styleoption requires v1.41 or later. - Browser engine: compare Chromium, Firefox, and WebKit separately. The WebKit scrollbar pseudo-element rule is non-standard, and the source documentation does not establish a universal engine-by-engine result.
- Operating system: scrollbar presentation can be part of the environment-specific rendering behavior; verify on the OS used by local development and CI.
- What scrolls: identify whether the document or a nested element is scrollable. Since
scrollbar-widthis not inherited, a rule applied only to an ancestor may not affect a nested scroller. - Capture mode: check whether you are capturing the viewport or using
fullPage: true. Full-page changes the capture extent, not the scrollbar styling guarantee. - Stylesheet timing: pass the
styleoption on the screenshot call that creates the file you are inspecting.
A report about Playwright 1.42.0 on Windows with Chrome described a full-page screenshot that omitted the scrollbar. Treat an individual environment report as a clue to reproduce in your own configuration, not as a rule for all current versions.
5. Common errors and fixes
| Symptom | Likely cause | Fix |
|---|---|---|
| The screenshot still shows a scrollbar. | The injected stylesheet is missing, the Playwright version is older than v1.41, or the rule does not match the element that scrolls. | Check the version and confirm that the screenshot call includes style. Apply scrollbar-width: none to the relevant elements; the universal rule in the example covers nested elements. |
| The page or panel no longer scrolls. | overflow: hidden was used to conceal the scrollbar. |
Remove that overflow rule. Use scrollbar styling so the scrolling behavior remains available. |
| One browser hides the scrollbar, another does not. | The CSS rules have different support or rendering behavior across engines. | Keep the standardized property and WebKit companion rule, then verify output in each browser engine your project actually uses. |
| A nested panel still has a scrollbar. | The CSS was set only on an ancestor, but scrollbar-width is not inherited. |
Apply the property directly to the scroller or use the universal selector from the example. |
| The image is unexpectedly tall or short. | The capture uses a different extent than intended, or nested content is independently scrollable. | Choose viewport versus fullPage deliberately, and set the nested panel’s state before capture if its contents must be visible. |
| The output differs between local runs and CI. | The browser engine, operating system, page-versus-element scrolling, or capture mode differs. | Compare those settings one at a time and reproduce with the same browser and OS configuration used in CI. |
6. Performance, reliability, and cost
The temporary stylesheet is a small CSS change applied during capture. The practical reliability work is to keep the browser engine, operating system, page state, and capture mode consistent wherever screenshots are compared. The sources provide no benchmark for the time or resource effect of this CSS, so measure in the project’s own capture workload if that matters.
Playwright’s browser setup and execution are part of a DIY screenshot workflow. If you need hosted screenshot delivery instead, compare the cost of maintaining that workflow with the number of captures and the service’s billing rules. For ScreenshotNeo’s published plans, the free plan includes 1,000 shots per month with no card; paid plans start at $5 for 3,000 shots. Plan details are available on ScreenshotNeo.
7. Or skip the browser setup
ScreenshotNeo returns a screenshot from one GET request. See the ScreenshotNeo API documentation for its options and response details.
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}`);
const bytes = Buffer.from(await res.arrayBuffer());
await import('node:fs/promises').then(fs => fs.writeFile('shot.webp', bytes));
ScreenshotNeo removes cookie and consent banners, newsletter popups, and chat widgets before the shot; each cleanup step can be turned off. Bot checks, blank pages, and failed loads are never billed, and responses identify the page verdict and billing status in headers. Its MCP server lets AI agents use take_screenshot, get_page_info, and capture_pdf. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000. Create a free ScreenshotNeo account.
8. FAQ
Does fullPage: true hide scrollbars?
No. It captures the full scrollable page instead of only the current viewport. Use screenshot CSS to control scrollbar visibility.
Will scrollbar-width: none stop a page from scrolling?
No. It hides the scrollbar while leaving the element scrollable. Avoid using overflow: hidden for this purpose.
Does the screenshot style option affect frames and Shadow DOM?
Playwright documents that the injected stylesheet pierces Shadow DOM and applies to inner frames. The option was added in v1.41. Check the API documentation for the version you use.
Should I hide scrollbars in the site’s permanent CSS?
Usually not for a screenshot-only need. Hiding visible scrolling controls can harm accessibility, so scope the style to the capture when that is the only requirement.


