Previous Article
PHP Sessions Done Properly: Security, Locking and Correct Logout
30 June 2018
A website responsiveness checker lets you preview a page at several viewport widths at once, so you can see how a layout reflows from desktop to tablet to phone without endlessly dragging a browser window. It is one of the most useful things you can build in an afternoon, and one of the most commonly misunderstood — because the naive version breaks on most of the sites you would want to test.
This guide walks through building a working responsive design testing tool, explains the browser security rule that limits it, and covers what viewport testing genuinely tells you versus what it cannot.
Every modern browser ships a device mode that does the core job for free. In Chrome or Edge, open DevTools and press Ctrl+Shift+M (Cmd+Shift+M on macOS). Firefox calls it Responsive Design Mode, on the same shortcut. You get viewport presets, device pixel ratio emulation, touch event simulation, and network throttling — considerably more than a homemade iframe tool will ever give you.
So build your own when you need something DevTools does not do:
Here is the part that sinks most tutorials on this topic. A responsiveness checker works by loading the target URL in an <iframe> and resizing that frame. But websites control whether they may be framed, and the majority of significant ones refuse.
Two mechanisms enforce this:
X-Frame-Options: SAMEORIGIN Content-Security-Policy: frame-ancestors 'self'
If a site sends either header, your iframe renders blank and the console logs a refusal. Google, Facebook, X, GitHub, most banks and virtually every SaaS dashboard block framing to prevent clickjacking. There is no client-side workaround — that is the entire point of the header.
This means a browser-based checker is honestly useful for:
frame-ancestors for your tool's originPublic "check any URL" services get around this with server-side headless browsers (Puppeteer or Playwright) that screenshot the page and return images. That is a different, heavier architecture — a real rendering service, not a front-end trick. Be clear about which you are building.
The markup is a device toolbar plus a frame container:
<div class="checker">
<form id="url-form" class="toolbar">
<input type="url" id="url" placeholder="https://example.com" required />
<button type="submit">Preview</button>
</form>
<div class="devices">
<button data-w="375" data-h="667">Phone</button>
<button data-w="768" data-h="1024">Tablet</button>
<button data-w="1440" data-h="900" class="active">Desktop</button>
</div>
<div class="stage">
<iframe id="frame" title="Responsive preview"></iframe>
</div>
</div>
The critical CSS trick is scaling. A 1440px-wide frame will not fit inside a 900px container, so instead of shrinking the iframe — which would change the viewport width the page sees, defeating the whole exercise — you render it at full size and scale it visually with a transform:
.stage {
display: flex;
justify-content: center;
overflow: hidden;
}
#frame {
border: 1px solid #d8dee9;
border-radius: 12px;
background: #fff;
transform-origin: top center;
transition: width .25s, height .25s, transform .25s;
}
And the JavaScript that ties it together:
const frame = document.getElementById('frame');
const stage = document.querySelector('.stage');
const form = document.getElementById('url-form');
let current = { w: 1440, h: 900 };
function render() {
const { w, h } = current;
// Scale down only when the frame is wider than the space available.
const scale = Math.min(1, stage.clientWidth / w);
frame.style.width = w + 'px';
frame.style.height = h + 'px';
frame.style.transform = `scale(${scale})`;
// A scaled element still reserves its unscaled height, so correct for it.
stage.style.height = (h * scale) + 'px';
}
document.querySelectorAll('.devices button').forEach((btn) => {
btn.addEventListener('click', () => {
document.querySelector('.devices .active')?.classList.remove('active');
btn.classList.add('active');
current = { w: +btn.dataset.w, h: +btn.dataset.h };
render();
});
});
form.addEventListener('submit', (e) => {
e.preventDefault();
const raw = document.getElementById('url').value.trim();
// Reject anything that is not http(s) — blocks javascript: and data: URLs.
let url;
try {
url = new URL(raw);
} catch {
return alert('Enter a valid URL.');
}
if (!['http:', 'https:'].includes(url.protocol)) {
return alert('Only http and https URLs are supported.');
}
frame.src = url.href;
});
window.addEventListener('resize', render);
render();
Two details worth keeping. The transform-origin: top center keeps the scaled frame anchored where you expect rather than drifting. And correcting stage.style.height matters because CSS transforms are purely visual — the browser still reserves the element's original layout box, so without that line you get a large gap under the preview.
You are loading arbitrary third-party pages into your own document. Constrain what they can do:
<iframe id="frame" title="Responsive preview" sandbox="allow-scripts allow-same-origin allow-forms" referrerpolicy="no-referrer"></iframe>
Note the genuine trade-off: allow-scripts together with allow-same-origin lets the framed page remove its own sandboxing if it shares your origin. For a tool previewing your own sites that is acceptable; for anything public-facing, drop allow-same-origin.
Do not chase individual device models — the list changes yearly and the exact pixel dimensions rarely matter. Test the widths where your own layout changes. If your CSS breaks at 640px, 768px and 1024px, those are your test widths, plus one just below and just above each.
A reasonable default set for viewport testing in 2026:
Resizing a frame tests layout. It does not test the things that most often make a site feel broken on a real phone:
srcset and prefer SVG for icons and logos.:hover are unreachable by touch. Guard them with @media (hover: hover).100vh misbehaves. Use 100dvh instead.For those, test on real hardware. Chrome supports remote debugging of an Android device over USB via chrome://inspect, and Safari on macOS can inspect a connected iPhone once Web Inspector is enabled in iOS settings.
No amount of testing rescues a page missing the viewport meta tag. Every responsive page needs this in the <head>:
<meta name="viewport" content="width=device-width, initial-scale=1" />
Without it, mobile browsers assume a roughly 980px-wide desktop layout and scale the whole thing down, which is why the page renders as a tiny unreadable version of the desktop site. Avoid user-scalable=no and maximum-scale=1 — both block pinch zoom and fail accessibility requirements.
Beyond that, prefer modern CSS that adapts without breakpoints at all. clamp() for fluid typography, grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)) for card layouts, and container queries when a component's size matters more than the viewport's, all reduce how many widths you need to check in the first place.
A custom responsiveness checker is a genuinely useful tool for previewing your own sites and client work across device widths, and it takes very little code — an iframe, a scale transform, and a set of presets. Just be clear-eyed about the constraint: X-Frame-Options and CSP frame-ancestors mean it will never work as a universal "test any URL" service without a server-side headless browser behind it. Use it for layout, and use real devices and DevTools throttling for everything else.
Advertisement
Written by
Amit VermaFounder / Senior Software Engineer
Amit leads engineering at Being Idea. With 15+ years building scalable software products across global markets, he drives architecture decisions and engineering culture across every engagement.
More articles by AmitYou might also like
We ship software that scales. Let's work together.