Screen Sizes for Responsive Design: Viewport Sizes to Test

For a responsive UI in 2026, test these screen sizes: 360x800 and 414x896 for phones, 768x1024 for a tablet, 1366x768 and 1536x864 for laptops, and 1920x1080 for desktops. Then add one width on each side of every breakpoint your CSS uses, such as 767 and 768. The sizes come from StatCounter's September 2026 data, read 23 October 2026.
Checked with Playwright 1.64.1 and Cypress 16.2.0 on 23 October 2026. Every file on this page ran as printed in an empty folder, and every output block is copied from that run's log.
Screen sizes to test in 2026
Mobile had 58.99% of the platform share worldwide in September 2026, desktop 39.26% and tablet 1.76%, according to StatCounter's platform share, read 23 October 2026. Within each class, these are the sizes worth a test run.
| Size | Device class | Share of its class, worldwide | Why it is on the list |
|---|---|---|---|
| 360x800 | Phone | 7.54% of mobile | The narrowest size in the mobile top six |
| 414x896 | Phone | 13.35% of mobile | The largest single mobile size, 19.6% in the US |
| 390x844 | Phone | 6.05% of mobile | 10.16% in the US |
| 768x1024 | Tablet | 8.09% of tablet | The largest tablet size, and the md breakpoint in Tailwind and Bootstrap |
| 1366x768 | Laptop | 7.95% of desktop | A short screen: content below the fold moves first |
| 1536x864 | Laptop | 10% of desktop | The second-largest desktop size |
| 1920x1080 | Desktop | 28.07% of desktop | The largest desktop size by far |
Sources: StatCounter mobile, tablet and desktop screen resolution stats for September 2026, worldwide, with the US figures from the US mobile page. All read 23 October 2026.
Pick by your own analytics where you have them, because the mix moves by market: 414x896 is 13.35% of mobile worldwide and 19.6% in the US. The table is the default when you have no data yet.
Width matters more than height for layout. The default breakpoints in Tailwind and Bootstrap are minimum widths, so width decides which layout renders. Height decides what is above the fold, which is why 1366x768 earns a place even though 1920x1080 has more users.
Screen size, viewport size and device pixels
StatCounter calls these numbers screen resolution, and its pages do not define them as the browser viewport. The viewport is the area the page actually gets inside the browser. Playwright's built-in device list records both sizes for each phone. This script prints three of its entries. Save it as print-devices.js in the Playwright folder from the section below, and run it after npm install:
const { devices } = require('@playwright/test');
for (const name of ['iPhone 15', 'Pixel 7', 'iPhone 15 Pro Max']) {
const d = devices[name];
console.log(`${name}: screen ${d.screen.width}x${d.screen.height}, viewport ${d.viewport.width}x${d.viewport.height}, deviceScaleFactor ${d.deviceScaleFactor}`);
}
node print-devices.js
iPhone 15: screen 393x852, viewport 393x659, deviceScaleFactor 3
Pixel 7: screen 412x915, viewport 412x839, deviceScaleFactor 2.625
iPhone 15 Pro Max: screen 430x932, viewport 430x739, deviceScaleFactor 3
In each of these entries the viewport width equals the screen width, and the viewport height is shorter. For the iPhone 15, the page gets 393x659 of a 393x852 screen. Test with viewport sizes, and treat a screen height from the table as an upper bound.
Viewport sizes are in CSS pixels, not the physical pixels on the glass. MDN's device pixel entry defines a CSS pixel as "exactly 1/96th of 1 CSS inch". Its devicePixelRatio page says the ratio "tells the browser how many of the screen's actual pixels should be used to draw a single CSS pixel". Both were read 23 October 2026. The deviceScaleFactor of 3 above means three device pixels for each CSS pixel across. The page also needs <meta name="viewport" content="width=device-width, initial-scale=1">, or a phone renders it at a desktop width and scales it down.
Responsive breakpoints to test around
A breakpoint is the width where your CSS switches layout. These are the defaults in Tailwind CSS and Bootstrap, read 23 October 2026 on the Tailwind CSS v4.3 docs and the Bootstrap 5.3 docs.
| Name | Tailwind CSS v4.3 | Bootstrap 5.3 |
|---|---|---|
| Below sm | under 640px | under 576px (xs) |
| sm | 640px (40rem) and up | 576px and up |
| md | 768px (48rem) and up | 768px and up |
| lg | 1024px (64rem) and up | 992px and up |
| xl | 1280px (80rem) and up | 1200px and up |
| 2xl / xxl | 1536px (96rem) and up | 1400px and up |
The screen sizes above tell you who visits. The breakpoints tell you where the layout can break. Test both: the common sizes, and one pixel either side of each breakpoint your own CSS uses. If your project changed the defaults, read the breakpoints from your config, not from this table.
The one-pixel rule matters because max-width: 767px and max-width: 768px look alike in review and differ at exactly one width: 768, the largest tablet size in the table. The Playwright section shows that bug caught by a test.
Set the viewport in Playwright
Start in an empty folder. The page under test is one HTML file whose nav collapses into a menu button below 768px. Save it as index.html:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Viewport demo</title>
<style>
body { margin: 0; font-family: sans-serif; }
nav { display: flex; gap: 16px; padding: 16px; }
#menu-button { display: none; }
@media (max-width: 767px) {
nav .links { display: none; }
#menu-button { display: block; }
}
</style>
</head>
<body>
<nav>
<button id="menu-button" aria-label="Open menu">Menu</button>
<div class="links"><a href="#pricing">Pricing</a> <a href="#docs">Docs</a></div>
</nav>
</body>
</html>
Then package.json:
{
"name": "viewport-demo",
"private": true,
"devDependencies": {
"@playwright/test": "1.64.1"
}
}
And playwright.config.js:
const { defineConfig } = require('@playwright/test');
module.exports = defineConfig({
testDir: './tests',
reporter: 'list',
use: { browserName: 'chromium' },
});
The test runs once per size. test.use({ viewport }) inside a describe block sets the size before the page opens, and the test asserts that the right nav shows. Save it as tests/viewports.spec.js:
const { test, expect } = require('@playwright/test');
const path = require('path');
const pageUrl = 'file://' + path.join(__dirname, '..', 'index.html');
const viewports = [
{ name: 'android', width: 360, height: 800, menu: true },
{ name: 'iphone', width: 414, height: 896, menu: true },
{ name: 'below-md', width: 767, height: 1024, menu: true },
{ name: 'tablet', width: 768, height: 1024, menu: false },
{ name: 'laptop', width: 1366, height: 768, menu: false },
{ name: 'laptop-scaled', width: 1536, height: 864, menu: false },
{ name: 'desktop', width: 1920, height: 1080, menu: false },
];
for (const vp of viewports) {
test.describe(`${vp.name} ${vp.width}x${vp.height}`, () => {
test.use({ viewport: { width: vp.width, height: vp.height } });
test('nav matches the breakpoint', async ({ page }) => {
await page.goto(pageUrl);
const width = await page.evaluate(() => window.innerWidth);
console.log(`${vp.width}x${vp.height}: innerWidth=${width}`);
if (vp.menu) {
await expect(page.getByRole('button', { name: 'Open menu' })).toBeVisible();
await expect(page.getByRole('link', { name: 'Pricing' })).toBeHidden();
} else {
await expect(page.getByRole('link', { name: 'Pricing' })).toBeVisible();
await expect(page.getByRole('button', { name: 'Open menu' })).toBeHidden();
}
});
});
}
npm install
npx playwright install chromium
npx playwright test
Running 7 tests using 1 worker
360x800: innerWidth=360
✓ 1 tests/viewports.spec.js:20:5 › android 360x800 › nav matches the breakpoint (65ms)
414x896: innerWidth=414
✓ 2 tests/viewports.spec.js:20:5 › iphone 414x896 › nav matches the breakpoint (49ms)
767x1024: innerWidth=767
✓ 3 tests/viewports.spec.js:20:5 › below-md 767x1024 › nav matches the breakpoint (45ms)
768x1024: innerWidth=768
✓ 4 tests/viewports.spec.js:20:5 › tablet 768x1024 › nav matches the breakpoint (48ms)
1366x768: innerWidth=1366
✓ 5 tests/viewports.spec.js:20:5 › laptop 1366x768 › nav matches the breakpoint (46ms)
1536x864: innerWidth=1536
✓ 6 tests/viewports.spec.js:20:5 › laptop-scaled 1536x864 › nav matches the breakpoint (44ms)
1920x1080: innerWidth=1920
✓ 7 tests/viewports.spec.js:20:5 › desktop 1920x1080 › nav matches the breakpoint (45ms)
7 passed (1.4s)
Now the off-by-one bug. Change max-width: 767px to max-width: 768px in index.html and run the same command. The 768x1024 test fails, because at exactly 768 the links are now hidden. The log below is trimmed to the error and the summary; the ... line marks the cut:
1) tests/viewports.spec.js:20:5 › tablet 768x1024 › nav matches the breakpoint ───────────────────
Error: expect(locator).toBeVisible() failed
Locator: getByRole('link', { name: 'Pricing' })
Expected: visible
Timeout: 5000ms
Error: element(s) not found
...
1 failed
6 passed (6.7s)
Only the 768 row caught it. A suite that tested 390 and 1280 would have passed.
Three more ways to set the size. In playwright.config.js, use: { viewport: { width: 1280, height: 720 } } sets it for every test, and a project can spread a device instead, such as ...devices['iPhone 13']. Inside a test, await page.setViewportSize({ width: 640, height: 480 }) resizes the page. The docs add that "you should set the viewport size before navigating to the page." With no setting at all, Playwright "Defaults to an 1280x720 viewport." Sources: Playwright emulation and page.setViewportSize, read 23 October 2026.
Set the viewport in Cypress
Copy the same index.html, with the breakpoint back at max-width: 767px, into a second empty folder. Add package.json:
{
"name": "viewport-demo-cypress",
"private": true,
"devDependencies": {
"cypress": "16.2.0"
}
}
And cypress.config.js:
const { defineConfig } = require('cypress');
module.exports = defineConfig({
e2e: {
supportFile: false,
viewportWidth: 1366,
viewportHeight: 768,
},
});
cy.viewport(width, height) sets the size for the current test. Cypress also ships named presets, and iphone-xr is 414x896, the largest mobile size in the table. Save the spec as cypress/e2e/viewports.cy.js:
const viewports = [
{ width: 360, height: 800, menu: true },
{ width: 414, height: 896, menu: true },
{ width: 767, height: 1024, menu: true },
{ width: 768, height: 1024, menu: false },
{ width: 1366, height: 768, menu: false },
{ width: 1536, height: 864, menu: false },
{ width: 1920, height: 1080, menu: false },
];
describe('nav at each viewport', () => {
viewports.forEach(({ width, height, menu }) => {
it(`${width}x${height}`, () => {
cy.viewport(width, height);
cy.visit('index.html');
cy.window().its('innerWidth').should('eq', width);
if (menu) {
cy.get('#menu-button').should('be.visible');
cy.contains('a', 'Pricing').should('not.be.visible');
} else {
cy.contains('a', 'Pricing').should('be.visible');
cy.get('#menu-button').should('not.be.visible');
}
});
});
it('iphone-xr preset', () => {
cy.viewport('iphone-xr');
cy.visit('index.html');
cy.window().its('innerWidth').should('eq', 414);
cy.get('#menu-button').should('be.visible');
});
});
npm install
npx cypress run --browser electron
nav at each viewport
✓ 360x800 (107ms)
✓ 414x896 (29ms)
✓ 767x1024 (25ms)
✓ 768x1024 (21ms)
✓ 1366x768 (25ms)
✓ 1536x864 (21ms)
✓ 1920x1080 (19ms)
✓ iphone-xr preset (17ms)
8 passing (308ms)
Without viewportWidth and viewportHeight in the config, Cypress opens tests at 1000x660. Its preset list runs from iphone-3 at 320x480 to macbook-16 at 1536x960, and a second argument of landscape swaps width and height. One limit: the docs say "devicePixelRatio is not simulated", so a test of a sharp image on a high-density screen needs Playwright's deviceScaleFactor or a real device. Sources: cy.viewport and the Cypress configuration reference, read 23 October 2026.
How Qodex fits
Qodex drives your real app in a browser, captures screenshots at desktop, tablet and phone widths on every run, and saves the run as Playwright you own. See Qodex UI testing or try it on your app. For the wider practice around layout checks, read our UI testing guide.
Summary
Test the common sizes your users have: 360 and 414 wide on phones, 768x1024 on a tablet, 1366x768, 1536x864 and 1920x1080 on laptops and desktops. Then test one pixel either side of each breakpoint your CSS uses. Set the size before the page loads, and assert on what the layout shows, not just that the page opened.
Frequently Asked Questions
What screen sizes should I test for responsive design?
Start with 360x800, 414x896, 768x1024, 1366x768, 1536x864 and 1920x1080. They cover the largest phone, tablet and desktop sizes in StatCounter's September 2026 data. Add the widths either side of each breakpoint in your CSS, and the sizes your own analytics show.
What are the most common viewport sizes in 2026?
StatCounter's September 2026 worldwide data puts 414x896 first on mobile at 13.35%, 768x1024 first on tablet at 8.09%, and 1920x1080 first on desktop at 28.07%. StatCounter calls these screen resolutions, so the usable viewport height is smaller once the browser's toolbars are drawn.
What is the difference between screen resolution and viewport size?
Screen resolution, as StatCounter reports it, describes the whole screen. The viewport is the area the page gets inside the browser, in CSS pixels. In Playwright's device list an iPhone 15 has a 393x852 screen and a 393x659 viewport.
What are the standard responsive breakpoints?
Tailwind CSS v4.3 uses 640, 768, 1024, 1280 and 1536px. Bootstrap 5.3 uses 576, 768, 992, 1200 and 1400px. Both treat a breakpoint as a minimum width, and both put md at 768px.
How do I set the viewport size in Playwright?
Use test.use({ viewport: { width: 390, height: 844 } }) for a group of tests, use.viewport in the config for every test, or page.setViewportSize() inside one test. Set it before the page navigates. The default is 1280x720.
How do I set the viewport size in Cypress?
Call cy.viewport(width, height) or cy.viewport('iphone-xr') in the test, or set viewportWidth and viewportHeight in cypress.config.js. The default is 1000x660.





