How to Make a Single HTML Page Mobile-Friendly

Most people who open your link will do it on a phone. If your page was designed on a laptop, there's a good chance it currently arrives zoomed out to illegibility, or with a horizontal scrollbar and a paragraph running off the right edge. Fixing that is a handful of changes, and the first one is a single line.

The one tag that fixes half of it

<meta name="viewport" content="width=device-width, initial-scale=1">

Without it, mobile browsers pretend to be about 980px wide and shrink the result to fit — which is why text ends up tiny and everything needs pinching. That behaviour exists for sites built before phones did. With the tag, the browser reports its real width and your CSS can respond to it.

Put it in <head>, and don't add user-scalable=no or maximum-scale=1. Blocking zoom breaks the page for anyone who needs to enlarge text, and browsers increasingly ignore it anyway.

Stop things overflowing sideways

Horizontal scrolling is the most common mobile complaint, and it's nearly always one of four culprits.

Fixed widths

/* breaks on any screen under 800px */
.container { width: 800px; }

/* adapts */
.container { width: 100%; max-width: 800px; margin: 0 auto; }

Images without a ceiling

img, video, iframe, table, pre {
  max-width: 100%;
}
img { height: auto; }

That one rule prevents a large number of layout problems. Code blocks and tables need extra care — give them their own scroll container so the block scrolls instead of the page:

.table-wrap, pre { overflow-x: auto; }

Padding pushing past the edge

/* 100% + 40px is wider than the screen */
.card { width: 100%; padding: 20px; }

/* padding counted inside the width */
*, *::before, *::after { box-sizing: border-box; }

Setting box-sizing: border-box globally is the standard first line of most stylesheets for exactly this reason.

Long unbroken strings

A URL or a long token can't wrap and forces the page wide:

body { overflow-wrap: break-word; }

Collapse multi-column layouts

Modern CSS can do this without a media query at all. This grid places as many 280px columns as fit and stacks to one automatically:

.grid {
  display: grid;
  gap: 1.5rem;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
}

Flexbox equivalent, when you want items to wrap:

.row { display: flex; flex-wrap: wrap; gap: 1.5rem; }
.row > * { flex: 1 1 280px; }

When you do need an explicit breakpoint, one is usually enough:

@media (max-width: 700px) {
  .hero { padding: 2rem 1rem; }
  .hero h1 { font-size: 1.8rem; }
  .sidebar { display: none; }
}

Type and spacing that survive a small screen

  • Body text at 16px minimum. Below that iOS Safari zooms form fields on focus, which jerks the layout around.
  • Headings that scale. font-size: clamp(1.8rem, 5vw, 3.5rem) gives you one rule instead of three breakpoints.
  • Line length. max-width: 65ch on paragraphs keeps text readable on wide screens without affecting narrow ones.
  • Side padding. At least 16px, or text runs into the bezel.
  • Tap targets at least 44×44px, with space between them. Adjacent small links are the main cause of mis-taps.

Respect the notch and the home bar

On phones with rounded corners, content can slide under the system UI. Two additions handle it:

<meta name="viewport"
      content="width=device-width, initial-scale=1, viewport-fit=cover">
body {
  padding-left: env(safe-area-inset-left);
  padding-right: env(safe-area-inset-right);
  padding-bottom: env(safe-area-inset-bottom);
}

Four mobile-only bugs worth knowing about

100vh is taller than the screen

On mobile browsers 100vh historically meant the viewport without the address bar, so a "full height" hero got cut off. The fix is a newer unit:

.hero {
  min-height: 100vh;    /* fallback for old browsers */
  min-height: 100dvh;   /* dynamic viewport height — respects the address bar */
}

Hover-only interactions are invisible

Touch screens have no hover. A menu, tooltip or caption that only appears on :hover is unreachable for most of your visitors. Either make it always visible on small screens, or trigger it on tap:

@media (hover: hover) {
  .card:hover .caption { opacity: 1; }
}
@media (hover: none) {
  .caption { opacity: 1; }   /* always shown on touch devices */
}

Fixed elements and the on-screen keyboard

A position: fixed footer or sticky call-to-action often ends up floating over the middle of the screen when the keyboard opens. If your page has a form, either avoid fixed positioning near it or hide the fixed element while an input is focused.

Fonts smaller than 16px in form fields

iOS Safari zooms the page in when you focus an input whose font is under 16px, and doesn't zoom back out. It looks like a bug in your layout, and the fix is one rule:

input, select, textarea { font-size: 16px; }

Test it properly

In order of how much they catch:

  1. On a real phone. Publish the page and open the link. Nothing substitutes for this — touch targets, scroll behaviour and font rendering all differ from the simulator.
  2. Browser device emulation. Chrome or Firefox devtools, responsive mode. Fast for iterating.
  3. Resize the window. Drag it narrow and watch what breaks first. Crude, and surprisingly effective.
  4. Lighthouse. Chrome devtools → Lighthouse flags viewport problems, small tap targets and illegible font sizes.

Being able to publish in a few seconds makes step one practical — deploy, open the link on your phone, fix, save, refresh. On hdply that loop is a browser editor and a save, with every version kept in case an edit makes things worse.

A minimal responsive skeleton

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>My page</title>
  <style>
    *, *::before, *::after { box-sizing: border-box; }
    body {
      margin: 0;
      font: 16px/1.6 system-ui, -apple-system, sans-serif;
      overflow-wrap: break-word;
    }
    .wrap { width: 100%; max-width: 720px; margin: 0 auto; padding: 2rem 1rem; }
    h1 { font-size: clamp(1.8rem, 5vw, 3rem); line-height: 1.15; }
    p { max-width: 65ch; }
    img { max-width: 100%; height: auto; }
    .grid {
      display: grid; gap: 1.5rem;
      grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
    }
  </style>
</head>
<body>
  <div class="wrap">
    <h1>Hello</h1>
    <p>This page is readable on any screen.</p>
  </div>
</body>
</html>

Related: Build a one-page portfolio · The meta tags every page should have

From a file on your desktop to a live URL in under a minute.

Deploy your first page