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.
- Marker.io: How to test your website on iPhone Duo
- WCAG 1.4.10 Reflow
- Bluetext: How to prepare your website for iPhone Duo
- Sizzy: How to find the breakpoints of any website
- web.dev: The large, small and dynamic viewport units
- MDN: <length> (viewport units)
- WCAG 2.5.8 Target Size (Minimum)
- web.dev: Accessible tap targets
- MDN: Responsive images
- web.dev: Responsive images
- MDN: Window.matchMedia()
- screensize.io: iPhone Duo viewport estimates
- MDN: position
- MDN: text-size-adjust
- MDN: CSS container queries
- MDN: Using media queries
- MDN: env()
- MDN: meta name=viewport
- WCAG 2.4.3 Focus Order
- WCAG 1.4.3 Contrast (Minimum)