Add viewport-fit=cover to the viewport meta tag, then pad with env(safe-area-inset-*). Without the meta tag the env() values are all zero and nothing happens — that is the bug people spend an afternoon on.
<!-- Without this, every env() value below is 0 -->
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
.app {
padding-top: env(safe-area-inset-top);
padding-bottom: env(safe-area-inset-bottom);
padding-left: env(safe-area-inset-left);
padding-right: env(safe-area-inset-right);
}
/* Keep your own spacing AND clear the system UI */
.toolbar {
padding-bottom: calc(1rem + env(safe-area-inset-bottom, 0px));
}
The second argument to env() is a fallback for browsers that do not know the variable. Use it — env(safe-area-inset-bottom, 0px) inside a calc() is the difference between working everywhere and collapsing to nothing on a desktop browser.
The values, and why you should not type them
The insets below are real and stable per display geometry. They are here so you can sanity-check a layout, not so you can paste 34px into a stylesheet — a device released next year with a different notch will make that number wrong, silently.
| Top | Bottom | Geometry | Devices | Examples |
|---|---|---|---|---|
| 62 px | 34 px | Dynamic Island, iPhone Air | 1 | iPhone Air |
| 62 px | 34 px | Dynamic Island, 6.9″ class | 2 | iPhone 17 Pro Max, iPhone 16 Pro Max |
| 62 px | 34 px | Dynamic Island, 6.3″ class | 3 | iPhone 17 Pro, iPhone 17, iPhone 16 Pro |
| 59 px | 34 px | Dynamic Island | 8 | iPhone 16 Plus, iPhone 16, iPhone 15 Pro Max |
| 48 px | 34 px | Notch | 4 | iPhone 11 Pro Max, iPhone 11, iPhone XR |
| 47 px | 34 px | Notch | 10 | iPhone 17e, iPhone 16e, iPhone 14 Plus |
| 44 px | 34 px | Notch | 5 | iPhone 13 mini, iPhone 12 mini, iPhone 11 Pro |
| 24 px | 20 px | iPad Pro 13″, home indicator | 2 | iPad Pro 13″ (M5), iPad Pro 13″ (M4) |
| 24 px | 20 px | iPad Pro 11″, home indicator | 6 | iPad Pro 11″ (M5), iPad Pro 11″ (M4), iPad Pro 11″ (4th gen) |
| 24 px | 20 px | iPad 13″, home indicator | 7 | iPad Air 13″ (M4), iPad Air 13″ (M3), iPad Air 13″ (M2) |
| 24 px | 20 px | iPad, home indicator | 7 | iPad Air 11″ (M4), iPad Air 11″ (M3), iPad Air 11″ (M2) |
| 24 px | 20 px | iPad mini, home indicator | 2 | iPad mini (7th gen), iPad mini (6th gen) |
| 20 px | 0 px | Home button, status bar only | 16 | iPhone SE (3rd gen), iPhone SE (2nd gen), iPhone SE (1st gen) |
The four failures
1. Forgetting viewport-fit=cover
Without it the browser lays out inside the safe area already, all four env() values report 0, and your carefully written padding does nothing. This is the most common cause of "env() does not work".
2. Hard-coding 44px or 34px
Those numbers are correct for one class of device. They are wrong on a home-button iPhone (0 at the bottom), wrong on an iPad (20), and wrong on desktop (0), where you have just added dead space for no reason.
3. Ignoring landscape
In landscape the notch moves to a side, so safe-area-inset-left or -right becomes non-zero — typically 44–48px — while the top inset drops. A layout that only pads top and bottom puts content underneath the notch as soon as the phone is rotated. Pad all four sides, always.
4. Using 100vh for a full-height layout
100vh on mobile is the height with the browser chrome hidden, so a full-height element is taller than the visible area until the user scrolls. The viewport units that fix it:
| Unit | Means | Use for |
|---|---|---|
svh | Small viewport height — chrome fully shown | Anything that must be fully visible on load. The safe default. |
lvh | Large viewport height — chrome fully hidden | Rarely what you want. Equivalent to the old vh behaviour. |
dvh | Dynamic — follows the chrome as it moves | Full-screen app shells. Note it reflows as the user scrolls, which can look restless. |
Combine them with the insets rather than choosing between them: min-height: 100svh for the shell, env() padding for the system UI inside it.
Questions
Do safe areas apply on Android?
Yes. Chrome on Android supports env(safe-area-inset-*) and returns non-zero values on devices with cutouts and gesture bars. The values differ from iOS, which is precisely the argument for using the variables instead of typing numbers.
What is the difference between the safe area and the status bar?
The status bar is one thing that occupies the top inset. The safe area is the union of everything the system reserves — status bar, notch or Dynamic Island, home indicator, rounded corners, and in landscape the camera housing.
Does env() work in a PWA installed to the home screen?
Yes, and it matters more there: a standalone PWA has no browser chrome to protect it, so the home indicator will sit on top of your bottom navigation unless you pad for it.
Can I read the safe area in JavaScript?
Not directly. Assign it to a custom property in CSS (--sat: env(safe-area-inset-top)) and read that with getComputedStyle.