iPhone Notch, Dynamic Island and PWA Fullscreen
Ever since the iPhone X with its notch and the iPhone 14 Pro with its Dynamic Island, web developers have had to make sure content does not disappear behind hardware elements. CSS env() together with the safe-area-inset-* variables is the standardized solution, working the same way across browsers, PWAs and native WebViews.
Table of Contents
- 1. Why safe area insets exist
- 2. viewport-fit=cover as the starting point
- 3. The CSS env() function
- 4. The four safe-area-inset directions
- 5. Combining env() with calc()
- 6. PWA fullscreen and standalone mode
- 7. Dynamic Island and newer iPhone models
- 8. Safe area insets compared
- 9. Tailwind CSS integration
- 10. Summary
- 11. FAQ
1. Why safe area insets exist
With the iPhone X, Apple introduced its first iPhone without a home button and with rounded display corners back in 2017. The display extends across almost the entire front of the device, interrupted only by the notch, the cutout for the camera and Face ID sensors. This design decision presented web developers with a new challenge: content that reached into the corners or under the notch was hidden behind hardware elements. The solution was CSS env() together with the safe-area-inset variables, which Apple initially introduced as a proprietary solution and which were later adopted into the CSS specification.
The principle is simple: when a website or PWA switches into fullscreen mode and thereby uses the entire screen, including the areas behind the notch and Dynamic Island, the operating system supplies distance values that indicate how far the "safe" area is from each screen edge. These values depend on the device and the orientation: in landscape mode the notch shifts to one side, producing different inset values than in portrait mode. CSS safe-area-inset-* makes these values available in CSS without requiring JavaScript or native code.
2. viewport-fit=cover as the starting point
Without a special setting in the viewport meta tag, Safari automatically adds margins on devices with a notch so that content does not extend under hardware elements. This default behavior is convenient, but it means the page has white margins on the sides and does not use the full screen space. Anyone who wants to use that space has to add viewport-fit=cover to the viewport meta tag. This attribute instructs the browser to expand the viewport to the entire screen, including the areas behind the notch and Dynamic Island.
Once viewport-fit=cover is active, the safe-area-inset-* variables are populated with correct values. Without this attribute, the inset values are always 0, because the browser is already handling spacing on its own. This makes it important to note: CSS env(safe-area-inset-*) only makes sense in combination with viewport-fit=cover. Anyone who sets viewport-fit=cover but forgets the env() variables ends up with a layout where content sits behind hardware elements, which is the most common implementation mistake.
/*
* Step 1: Enable viewport-fit=cover in HTML:
* <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
*
* Step 2: Use env() variables in CSS
*/
/* Basic safe area padding, all four sides */
.full-bleed-layout {
padding-top: env(safe-area-inset-top);
padding-right: env(safe-area-inset-right);
padding-bottom: env(safe-area-inset-bottom);
padding-left: env(safe-area-inset-left);
}
/* Shorthand with logical properties */
.app-shell {
padding-block-start: env(safe-area-inset-top);
padding-block-end: env(safe-area-inset-bottom);
padding-inline-start: env(safe-area-inset-left);
padding-inline-end: env(safe-area-inset-right);
}
/* Old syntax for compatibility (iOS 11.0 - 11.1) */
.legacy-support {
padding-top: constant(safe-area-inset-top);
padding-top: env(safe-area-inset-top);
}
3. The CSS env() function
The env() function in CSS works similarly to var() for custom properties, but it accesses environment variables defined by the user agent (browser or OS) instead of self-declared CSS variables. The syntax is identical: env(variable-name, fallback). The fallback value is optional and is used when the environment variable is not available, for example on desktop devices without a notch, or in browsers that do not support env().
One important difference from var(): CSS env() can be used anywhere in a CSS value, including inside calc() expressions. The specification defines a set of predefined environment variables, of which the safe-area-inset-* variables are the best known. Browsers can implement additional ones, for example for various UI elements. The variables are specified in the coordinate system of the screen, not relative to the viewport origin.
4. The four safe-area-inset directions
There are four safe-area-inset environment variables: safe-area-inset-top, safe-area-inset-right, safe-area-inset-bottom, safe-area-inset-left. In portrait orientation on the iPhone X, safe-area-inset-top is roughly 44 pixels (the notch height), safe-area-inset-bottom is roughly 34 pixels (the home indicator area), and the side insets are zero. In landscape orientation the notch shifts to one side, so safe-area-inset-left or safe-area-inset-right gets a value while safe-area-inset-top becomes smaller.
For fixed elements such as navigation bars, FABs (floating action buttons) and bottom navigation bars, these values are critical. A fixed header needs padding-top: env(safe-area-inset-top) so the text does not extend under the notch. A bottom bar needs to set padding-bottom: env(safe-area-inset-bottom) so content stays above the home indicator area. Without these adjustments, interactive elements become inaccessible or visually obscured by hardware overlays.
/* Fixed header respecting notch and status bar */
.site-header {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 100;
/* Combine existing padding with safe area inset */
padding-top: calc(1rem + env(safe-area-inset-top, 0px));
padding-left: calc(1.5rem + env(safe-area-inset-left, 0px));
padding-right: calc(1.5rem + env(safe-area-inset-right, 0px));
background: white;
border-bottom: 1px solid rgb(226 232 240);
}
/* Bottom navigation bar, above home indicator */
.bottom-nav {
position: fixed;
bottom: 0;
left: 0;
right: 0;
padding-bottom: env(safe-area-inset-bottom, 0px);
padding-left: env(safe-area-inset-left, 0px);
padding-right: env(safe-area-inset-right, 0px);
background: white;
border-top: 1px solid rgb(226 232 240);
}
/* Floating action button, safe from all notch areas */
.fab {
position: fixed;
bottom: calc(1.5rem + env(safe-area-inset-bottom, 0px));
right: calc(1.5rem + env(safe-area-inset-right, 0px));
}
5. Combining env() with calc()
Combining CSS env() with calc() is the core of a correct safe area implementation. It is rarely correct to simply set padding: env(safe-area-inset-top): most of the time an element needs its own padding, to which the safe area inset still needs to be added. The pattern calc(1rem + env(safe-area-inset-bottom, 0px)) adds 16 pixels of its own padding to the inset value. The fallback value 0px in env() matters here: on devices without safe area support, env() without a fallback returns an invalid value, which invalidates the entire calc() calculation.
Another useful pattern is using CSS custom properties as an intermediate layer for safe area values. By writing the env() values into custom properties, they can be used flexibly across components without referencing safe-area-inset directly everywhere. This pattern is especially helpful in design system contexts and in Tailwind-based projects where the values are meant to be used as tokens.
/* CSS custom properties as safe-area tokens */
:root {
--sat: env(safe-area-inset-top, 0px);
--sar: env(safe-area-inset-right, 0px);
--sab: env(safe-area-inset-bottom, 0px);
--sal: env(safe-area-inset-left, 0px);
}
/* Use tokens in components */
.page-content {
/* Content starts below header + notch */
padding-top: calc(var(--header-height, 4rem) + var(--sat));
/* Content above bottom nav + home indicator */
padding-bottom: calc(var(--bottom-nav-height, 4rem) + var(--sab));
/* Side padding respects landscape notch */
padding-left: max(1.5rem, calc(1.5rem + var(--sal)));
padding-right: max(1.5rem, calc(1.5rem + var(--sar)));
}
/* Scrollable content with safe area at bottom */
.scroll-container {
overflow-y: auto;
/* Reserve space at the bottom for safe area within scroll */
scroll-padding-bottom: var(--sab);
}
/* Safe padding shorthand using CSS logical properties */
.card-full-bleed {
padding: var(--sat) var(--sar) var(--sab) var(--sal);
}
6. PWA fullscreen and standalone mode
Progressive Web Apps in standalone mode behave like native apps: they have no browser chrome, no address bar, and they use the entire screen. In this mode, CSS safe-area-inset-* variables become especially important because the browser no longer inserts an automatic margin. Without correct inset handling, the PWA content extends directly into the notch or under the home indicator area.
The web app manifest must set "display": "standalone" or "display": "fullscreen". In fullscreen mode, viewport-fit=cover is implicitly active and safe-area-inset-* returns correct values. In standalone mode it is also active, but the browser status bar may still be visible depending on the device. The CSS media query @media (display-mode: standalone) makes it possible to apply safe area styles only in PWA mode and use different values for browser display.
7. Dynamic Island and newer iPhone models
The Dynamic Island, introduced with the iPhone 14 Pro, is technically a software extension of the physical hardware cutout for the front camera. From a CSS perspective it behaves similarly to the notch: safe-area-inset-top has a value that ensures content does not extend behind the Dynamic Island. The concrete pixel value is somewhat larger on these devices than on notch iPhones, but the implementation principle stays identical.
One important aspect: the Dynamic Island is interactive and can expand when system features such as timers, calls, or Live Activities are active. In that state it takes up more space, and the safe area inset value grows accordingly. Because CSS env() updates dynamically, a correctly implemented layout adapts automatically, with no extra code needed. This is a significant advantage over hardcoded pixel values or JavaScript-based detection.
8. Safe area insets compared
Different approaches to handling the notch and Dynamic Island each have their own advantages and drawbacks. The following table compares the most important methods.
| Approach | Dynamic | Device independent | Recommendation |
|---|---|---|---|
| CSS env() safe-area-inset-* | Yes | Yes | Primary solution, the standard |
| Fixed pixel margins | No | No | Not recommended, breaks on new models |
| JS window.screen detection | Limited | Limited | Last resort for legacy code |
| viewport-fit=auto (default) | n/a | Yes | When no fullscreen layout is needed |
| Native app WebView | Yes | Yes | Same as browser, env() works identically |
For completeness: CSS env() is not limited to safe-area-inset-*. The specification defines further variables such as keyboard-inset-* (for the virtual keyboard), which are available in modern browsers. The foundation stays the same: enable viewport-fit=cover, then work with env() and calc().
9. Tailwind CSS integration
In Tailwind-based projects, CSS safe-area-inset-* values can be made accessible through custom theme extensions. In Tailwind CSS v4 with its CSS-first approach, utility classes are defined directly in CSS. The pattern is to set env(safe-area-inset-*) as a custom property on :root and then use it through CSS variables inside Tailwind utilities. Alternatively, many Tailwind plugins such as tailwind-safe-area offer ready-made utility classes for safe area padding.
In Hyva Themes projects using Tailwind CSS v4, the recommended approach is to declare safe area tokens in the global CSS file and then combine them in template code using inline styles or custom utility classes. Since Hyva uses Alpine.js, safe area values can also be read reactively through JavaScript and exposed as Alpine data, which is helpful for complex layout calculations but unnecessarily complex for simple padding use cases.
/* Tailwind v4 CSS-first safe area utilities */
@layer utilities {
.pt-safe {
padding-top: env(safe-area-inset-top, 0px);
}
.pr-safe {
padding-right: env(safe-area-inset-right, 0px);
}
.pb-safe {
padding-bottom: env(safe-area-inset-bottom, 0px);
}
.pl-safe {
padding-left: env(safe-area-inset-left, 0px);
}
.p-safe {
padding-top: env(safe-area-inset-top, 0px);
padding-right: env(safe-area-inset-right, 0px);
padding-bottom: env(safe-area-inset-bottom, 0px);
padding-left: env(safe-area-inset-left, 0px);
}
/* Additive: combine with existing padding */
.pb-safe-4 {
padding-bottom: calc(1rem + env(safe-area-inset-bottom, 0px));
}
.pb-safe-6 {
padding-bottom: calc(1.5rem + env(safe-area-inset-bottom, 0px));
}
}
/* Media query: apply only in standalone PWA mode */
@media (display-mode: standalone) {
.pwa-header {
padding-top: env(safe-area-inset-top, 0px);
}
}
Mironsoft
Mobile-first CSS, PWA development and iOS layout optimization
Need layouts that work correctly on iPhone notch and Dynamic Island?
We implement correct safe area handling for websites and PWAs, from viewport-fit=cover through env() integration to Tailwind utility classes for all four inset directions.
iOS audit
Checking all fixed elements for correct safe area handling on current iPhone models
PWA optimization
Fullscreen PWA with correct safe area insets in standalone and fullscreen mode
CSS system
Anchoring Tailwind utilities and CSS tokens for safe area values in your design system
10. Summary
CSS env() together with the safe-area-inset-* environment variables is the standardized and recommended method for displaying layouts correctly on devices with a notch, Dynamic Island, or rounded display corners. The implementation follows a clear pattern: enable viewport-fit=cover in the viewport meta tag, then read safe-area-inset-top, -right, -bottom and -left with env() and combine them with your own padding inside calc() expressions. Fallbacks in env(variable-name, 0px) protect devices without safe area support.
Fixed elements such as headers, bottom navigation and floating action buttons deserve special attention, since they are the most directly affected by the notch and Dynamic Island. For PWAs in standalone mode, correct safe area implementation is not optional but necessary to achieve native app quality. Combining CSS env() with CSS custom properties as an intermediate layer, plus an optional Tailwind utility extension, results in a maintainable, cross-device system.
CSS env() and Safe Area Insets: The Essentials at a Glance
Prerequisite
Enable viewport-fit=cover in the meta tag. Without it, all safe-area-inset-* values are always 0.
Four directions
safe-area-inset-top, -right, -bottom, -left. Orientation dependent: different values in landscape than in portrait.
calc() combination
calc(1rem + env(safe-area-inset-bottom, 0px)), your own padding plus inset. The 0px fallback in env() is mandatory.
Dynamic Island
Behaves like the notch for CSS. safe-area-inset-top adjusts automatically, even when the Dynamic Island is expanded.