The meta viewport tag: the one line every site needs
Copy the meta viewport tag every site needs, learn what each part does, where it goes in Next.js, Vite and plain HTML, and which settings to avoid.
Put <meta name="viewport" content="width=device-width, initial-scale=1"> in the <head> of every page. It tells phones to lay the page out at the screen's real width, 402px on an iPhone 17 Pro, instead of pretending to be a roughly 980px desktop and shrinking everything down. That one line is the fix; the rest of this post explains what it does, what to add for notched phones and what to leave out.
The line
<meta name="viewport" content="width=device-width, initial-scale=1">
Copy it exactly. Commas between the settings, not semicolons. One tag per page, in the <head>, in the HTML your server sends.
What happens without it
Mobile browsers were born into a web full of desktop sites. To cope, they assume a page was designed for a big screen unless it says otherwise. They lay it out in a virtual viewport about 980px wide, then shrink the result to fit the phone. MDN's viewport meta tag page describes exactly this.
On an iPhone 17 Pro, which is 402 CSS pixels wide, that means drawing a 980px page at about 41% of its size. Your nice 16px body text shows up like 6.5px text. People have to pinch and zoom to read anything.
Worse, your CSS thinks it's on a 980px screen. Every @media (max-width: 768px) rule is skipped, every Tailwind md: class applies, and your carefully built mobile layout never appears. If that's what you're seeing, read Why your website looks zoomed out on phones too.
What each part does
| Part | What it does |
|---|---|
name="viewport" |
Tells the browser this meta tag is about the viewport. |
width=device-width |
Makes the layout viewport as wide as the device in CSS pixels: 402 on an iPhone 17 Pro, 360 on a Galaxy S25, 412 on a Pixel 10, 820 on an iPad Air 11″. |
initial-scale=1 |
Starts the page at 100% zoom, so one CSS pixel on the page is one CSS pixel on the screen. No zooming in or out on load. |
"CSS pixels" is doing a lot of work there. A phone's CSS width is much smaller than its resolution: the iPhone 17 Pro's 1206-pixel-wide panel is 402 CSS pixels because of its device pixel ratio of 3. What is a viewport? explains the difference, and the iPhone, Samsung Galaxy and Google Pixel pages list every phone's width.
You'll also see a few other keys in the wild:
heightsets the viewport height. You almost never want it.minimum-scaleandmaximum-scalelimit zooming. Leave them out (see below).user-scalableturns pinch-zoom on or off. Leave it out too.viewport-fitcontrols notches and rounded corners. Useful, covered below.interactive-widgetcontrols what the on-screen keyboard does. Also covered below.
Don't block zoom
AI tools and old templates love this version:
<!-- Don't do this -->
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
Please don't. Blocking zoom stops people with low vision from enlarging text, and WCAG's Resize Text rule expects text to be resizable to 200%. MDN recommends allowing at least 5× zoom. Lighthouse's accessibility audit flags user-scalable=no and a maximum-scale below 5.
It doesn't even work well. Since iOS 10, Safari ignores both user-scalable=no and maximum-scale by default. So on iPhones it doesn't stop anyone pinch-zooming, and on Android it only locks out the people who need zoom.
Framework docs sometimes show maximumScale: 1 and userScalable: false in examples, just to list every option. Don't copy those two lines.
The real reason people add it: input zoom on iPhone
Usually maximum-scale=1 gets added to stop one annoying thing: iPhone Safari zooms into the page when you tap a form field whose text is smaller than 16px. The fix is in CSS, not in the viewport tag:
input,
select,
textarea {
font-size: max(16px, 1rem);
}
At 16px or more, Safari doesn't zoom on focus, and everyone can still pinch-zoom when they want to.
Notches and the Dynamic Island: viewport-fit=cover
iPhones have rounded corners, a notch or Dynamic Island, and a home indicator bar. By default Safari keeps your page inside the "safe area", away from all of them. In landscape that means empty strips down the sides, filled with your page's background colour.
If you want your design to run edge to edge, opt in with viewport-fit=cover:
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
Now it's your job to keep text and buttons out of the danger zones, using the safe-area environment variables:
.site-header {
padding-top: env(safe-area-inset-top);
padding-left: max(16px, env(safe-area-inset-left));
padding-right: max(16px, env(safe-area-inset-right));
}
.bottom-bar {
padding-bottom: max(12px, env(safe-area-inset-bottom));
}
Each env() value is 0 where nothing is in the way, so this is safe on every device. max() gives you normal padding on a plain screen and extra room next to a notch or home indicator. The WebKit team's original write-up, Designing Websites for iPhone X, still explains it best, and MDN's env() page has the details.
Chrome on Android uses the same opt-in. From Chrome 135 the page can extend under Android's gesture navigation bar when you set viewport-fit=cover; Google's edge-to-edge guide shows how safe-area-inset-bottom keeps your content clear of it.
Only add viewport-fit=cover if you also add the padding. Otherwise you've invited content under the Dynamic Island. Fixed bottom bars need this most; there's a full example in 100vh on mobile is broken.
interactive-widget: what the keyboard does
Since Chrome 108, Chrome on Android matches Safari: when the on-screen keyboard opens, it shrinks only the visual viewport (the part you can see), not the layout viewport, so your page doesn't reflow. That's usually what you want.
If you're building something like a chat app and want the layout to shrink so your input sits above the keyboard, the interactive-widget key changes that:
<meta name="viewport" content="width=device-width, initial-scale=1, interactive-widget=resizes-content">
The values are resizes-visual (the default), resizes-content and overlays-content. Chrome explains them in its viewport resize post. Support outside Chromium is limited, so check MDN before relying on it.
Where to put it
Plain HTML
Near the top of <head>, right after the charset:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>My site</title>
</head>
<body>
...
</body>
</html>
Next.js (App Router)
Next.js adds the default viewport tag for you, so you usually don't need to do anything. If you want to change it, for example to add viewport-fit=cover, export a viewport object from app/layout.tsx. Since Next.js 14 it lives there, not inside metadata (Next.js docs).
// app/layout.tsx
import type { Viewport } from 'next'
export const viewport: Viewport = {
width: 'device-width',
initialScale: 1,
viewportFit: 'cover',
}
Don't also add a manual <meta name="viewport"> in your layout, or you'll end up with two.
Next.js (Pages Router)
Put it in pages/_app.tsx with next/head. Next.js warns you if you put it in _document.
// pages/_app.tsx
import Head from 'next/head'
import type { AppProps } from 'next/app'
export default function App({ Component, pageProps }: AppProps) {
return (
<>
<Head>
<meta name="viewport" content="width=device-width, initial-scale=1" />
</Head>
<Component {...pageProps} />
</>
)
}
Vite + React (and many AI builders)
Many AI app builders generate Vite + React projects. The tag belongs in index.html at the project root, not in a component. Vite's starter templates already include it:
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
If your project has lost it, put it back there.
WordPress and Shopify
Themes almost always include it: in WordPress it's in the theme's header.php, in Shopify it's in layout/theme.liquid. If you're using a very old or custom theme, check.
Check it's working
In the browser console on your live page:
document.querySelectorAll('meta[name="viewport"]').length; // should be 1
document.querySelector('meta[name="viewport"]')?.content; // should include width=device-width
Then open the page on an actual phone. Text should be readable without zooming, and your mobile layout should show. Chrome DevTools' device mode also respects the tag, though it has other blind spots (see why DevTools device mode isn't accurate).
Getting the tag right is step one. Step two is making sure nothing on the page is wider than the phone it's on.
Does your site fit?
Paste your address (or localhost:3000). doesitfit shows it on real screen sizes at once and tells you, bluntly, where it spills.
Free, no sign-up. Or ask Claude, ChatGPT or Codex to check it.
A prompt for your AI tool
If you built the site with Claude, ChatGPT, Cursor, Lovable, v0 or Bolt, paste this in:
Check my project's meta viewport tag. Every page should have exactly one, in the
<head>, reading<meta name="viewport" content="width=device-width, initial-scale=1">. If this is a Next.js App Router project, don't add a manual tag; use theviewportexport inapp/layoutinstead. If this is a Vite project, it belongs in the rootindex.html. Removemaximum-scaleanduser-scalable=noif they're there. If they were added to stop iPhone zooming into form fields, setinput,selectandtextareatofont-size: max(16px, 1rem)instead. Don't addviewport-fit=coverunless you also addenv(safe-area-inset-*)padding to fixed headers and bottom bars. Show me exactly what you changed.
The takeaway
- Every page needs
<meta name="viewport" content="width=device-width, initial-scale=1">, once, in the<head>. - Never block zoom. Fix iPhone input zoom with 16px form text instead.
- Add
viewport-fit=coveronly with safe-area padding. - In Next.js, use the
viewportexport; in Vite, useindex.html. - Check it on a real phone or with /whats-my-viewport, then test your layout across real screen sizes.
For the bigger picture of getting an AI-built site to behave on phones, see Make your AI-built website mobile friendly.
Questions people ask
Do I still need the viewport tag if I use Tailwind or Bootstrap?
Yes. CSS frameworks don't add it for you. Without it, phones lay the page out at about 980px, so your sm: and md: styles never kick in on a phone.
Is initial-scale=1.0 different from initial-scale=1?
No, they mean exactly the same thing. Vite's templates write 1.0, most docs write 1; both are fine.
Do desktop browsers use the meta viewport tag?
No. Desktop browsers ignore it and use the window size as the viewport. It only affects phone and tablet browsers, which is why a missing tag can go unnoticed until someone opens the site on a phone.
Does the viewport tag matter for SEO?
Indirectly. Google indexes the mobile version of pages, and without the tag a phone renders your page as a shrunken desktop page. Lighthouse also flags a missing or broken viewport tag.