Field guide

Problems, tips and tricks

Typical layout and accessibility problems on the iPhone Duo, and how to fix them. The Duo is about 466 px wide folded and about 890 px unfolded, at 3x. These sizes are third-party estimates until Apple publishes them. Safari has no Viewport Segments API, so you cannot detect the fold from CSS or JavaScript.

Back to the simulator

Horizontal overflow

Problem

One element wider than the viewport makes the whole page scroll sideways. It shows up first at the folded width of 466 px, and again when Split View drops Safari to about 445 px.

Fix

Find the element that is too wide instead of hiding the symptom with overflow-x: hidden on body. Typical culprits are fixed widths, images and video without max-width, wide tables, long unbroken strings and grids that never wrap. Let flex and grid items shrink and wrap, and give wide tables their own scroll container. Check at 466, 890 and 445 px.

img, video, iframe { max-width: 100%; height: auto; }
.row { display: flex; flex-wrap: wrap; gap: 1rem; }
.row > * { min-width: 0; }          /* lets flex items shrink */
p, li { overflow-wrap: anywhere; }  /* long URLs and words */
.table-wrap { overflow-x: auto; }   /* scroll the table, not the page */

Sources

The dead zone between breakpoints

Problem

Layouts that only switch at 768 px and 1024 px treat everything below 768 px as a phone and everything above as a tablet. The Duo sits at 466 px folded and 890 px unfolded, so it can get a stretched phone layout or a cramped tablet one.

Fix

Place breakpoints where your content breaks, not where a device class starts. Use fluid type, spacing and grids so layouts hold across the whole range. Test just below, at and just above every breakpoint you have, plus the middle range, and test the jump between 466 and 890 px without reloading.

.cards {
  display: grid;
  gap: 1rem;
  /* columns appear as soon as there is room, no device breakpoint */
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 16rem), 1fr));
}

Sources

Viewport units: 100vh vs dvh, svh, lvh

Problem

On Safari, 100vh matches the viewport with the browser bars retracted. With the bars showing, a 100vh section is taller than what you see, so buttons at the bottom end up off screen. The unfolded screen is only 626 px tall, which makes this worse.

Fix

Use svh when the content must always fit, lvh when it should fill the screen once the bars retract, and dvh when it should follow the bars live. dvh can change while scrolling, so avoid it on large animated areas. Prefer min-height over height, and avoid full-screen sections with fixed heights. Keep a 100vh fallback for old browsers.

.hero {
  min-height: 100vh;   /* fallback */
  min-height: 100svh;  /* always fits, bars shown */
}

Sources

Tap targets

Problem

Small or crowded links and buttons are hard to hit with a thumb. On the unfolded screen, controls near the fold are harder still, and Safari gives you no API to find the fold.

Fix

Aim for at least 44 px by 44 px for touch controls, with some space between them. WCAG 2.2 sets a floor of 24 CSS px, which is the minimum to pass, not the goal. Add padding instead of enlarging icons. Keep primary actions away from the center of the unfolded screen where you can.

button, .icon-btn, nav a {
  min-width: 44px;
  min-height: 44px;
  padding: 0.75rem;
}
.toolbar { display: flex; gap: 8px; }

Sources

Images at 3x

Problem

The Duo has a pixel ratio of 3. A 1x image looks soft, and a huge desktop image sent to every device wastes data. A missing sizes attribute makes the browser assume the image fills the full viewport width.

Fix

Offer several widths with srcset and tell the browser how wide the image will really be with sizes. The browser multiplies by the pixel ratio and picks a file. For a full-width image at 466 px that means about 1398 px of source. Set width and height to avoid layout shift.

<img
  src="photo-800.jpg"
  srcset="photo-480.jpg 480w, photo-800.jpg 800w,
          photo-1400.jpg 1400w, photo-2000.jpg 2000w"
  sizes="(width <= 600px) 100vw, 50vw"
  width="800" height="600" alt="Describe the photo">

Sources

Resize handlers: the fold changes the viewport live

Problem

Unfolding or folding resizes the page between 466 and 890 px while it is open, with no reload and no rotation event you can rely on. Code that reads the width once at load, or sniffs the user agent, is wrong after the first fold.

Fix

Do layout in CSS where you can. When JavaScript must react, listen to matchMedia change events or ResizeObserver rather than caching window.innerWidth. Never branch on the user agent. After a change, re-read sizes, rebuild only what depends on them, and keep state such as scroll position, form input and open menus.

const wide = matchMedia("(width >= 700px)");
const apply = () => document.body.classList.toggle("wide", wide.matches);
wide.addEventListener("change", apply);
apply();

Sources

Fixed and sticky elements

Problem

Sticky headers, cookie bars and bottom toolbars take a fixed slice of the screen. At 626 px unfolded height or 678 px folded, they can cover most of the content, especially with the on-screen keyboard open.

Fix

Keep fixed bars to a small share of the height. Let them scroll away or collapse in landscape, and use scroll-margin-top so anchors are not hidden behind a sticky header. Test with the keyboard open and with Safari bars in both states.

.site-header { position: sticky; top: 0; }
@media (height < 500px) {
  .site-header { position: static; }  /* short viewport: let it scroll */
}
:target, h2[id] { scroll-margin-top: 4rem; }

Sources

Text size and zoom

Problem

Safari zooms into any input with a font size under 16 px when you focus it, and the page stays zoomed afterwards. Mobile browsers can also inflate text on pages that are not set up for narrow screens.

Fix

Use at least 16 px (1rem) in form fields. Do not disable zoom with maximum-scale or user-scalable. Size text in rem so it follows the reader's settings. Include the viewport meta tag, and set text-size-adjust to 100% so Safari does not inflate text on its own. Do not rely on it to fix a layout that needs it.

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

html { -webkit-text-size-adjust: 100%; text-size-adjust: 100%; }
input, select, textarea { font-size: 1rem; }  /* 16px, no focus zoom */

Sources

Media queries and container queries

Problem

Media queries look at the viewport, but components sit in columns, sidebars and Split View panes. The same card can be 445 px or 890 px wide on the same page, and a viewport query cannot tell the difference.

Fix

Make the component respond to the space it gets, with container queries. Keep media queries for page-level layout and use range syntax, such as (width >= 600px) or (500px <= width <= 900px), to target widths cleanly instead of stacking min-width and max-width. Choose values from where the content breaks.

.card-list { container-type: inline-size; }

.card { display: block; }
@container (width >= 30rem) {
  .card { display: grid; grid-template-columns: 8rem 1fr; gap: 1rem; }
}

@media (500px <= width <= 900px) {
  .page { padding-inline: 2rem; }
}

Sources

Safe areas

Problem

Rounded corners and system UI can cover content that sits at the screen edge. Edge-to-edge layouts, bottom bars and full-bleed headers are the usual victims, in either screen.

Fix

Opt in with viewport-fit=cover only if you want to draw to the edges. Then pad fixed elements with the env(safe-area-inset-*) values. They are 0 where nothing intrudes, so adding them is safe. Test both screens and Split View.

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

.bottom-bar {
  padding-bottom: calc(0.75rem + env(safe-area-inset-bottom));
  padding-inline: max(1rem, env(safe-area-inset-left));
}

Sources

Accessibility basics

Problem

A new layout can change reading order, hide focus, lower contrast on a new background, or force two-way scrolling. Folding adds a second layout to keep accessible.

Fix

Keep DOM order logical, so focus moves in a sensible sequence in both layouts, and show a visible focus ring. Reach 4.5:1 contrast for normal text and 3:1 for large text. Content must reflow at a 320 px width without sideways scrolling (WCAG 1.4.10), which is narrower than the Duo, so a layout that passes at 445 px is not enough. Touch controls need 24 px at the least (WCAG 2.5.8) and 44 px as a goal.

:focus-visible { outline: 3px solid currentColor; outline-offset: 2px; }
/* check at 320px: no horizontal scrollbar */
.layout { min-width: 0; }

Sources

Checklist

Tick items as you go. Your ticks stay in this browser only.

Credits and sources

Every tip is a summary in our own words. Read the originals for the details.

Back to the simulator