CSS Environment Variables and env() Explained
CSS env() reads values from the browser or device itself, like safe-area insets on notched phones, instead of from your own custom properties.
CSS environment variables, accessed with the env() function, expose values that come from the user agent or the device itself — not from your stylesheet. The most common use is reading the safe-area insets on phones with notches, camera cutouts, or rounded corners, so a full-bleed layout can pad itself away from those physical obstructions without knowing the exact dimensions of every device it might run on.
env() vs var(): a source, not a scope
It’s easy to confuse env() with var(), since both plug a named value into a property, but they pull from different places. var() reads a custom property you defined yourself, cascading and inheriting like any other CSS value. env() reads a value the browser defines — you don’t set it, and it isn’t scoped to an element in the DOM tree the way a custom property is. It behaves more like a global constant supplied by the platform than a stylesheet variable.
:root {
--brand-color: #6366f1; /* your own custom property, read with var() */
}
body {
padding-top: env(safe-area-inset-top); /* platform value, read with env() */
color: var(--brand-color);
}
Safe-area insets
The flagship use case is the four safe-area inset variables — safe-area-inset-top, -right, -bottom, and -left — which report how much space is occupied by non-rectangular parts of a device’s screen: the notch or dynamic island at the top, the home indicator bar at the bottom, rounded display corners, and similar. On a device with none of these features, every inset resolves to 0px, so the same CSS works everywhere without a device-specific branch.
.app-header {
padding-top: env(safe-area-inset-top, 20px);
}
That 20px is a fallback value — env() accepts a second argument just like var() does, used when the environment variable isn’t supported or the browser reports no inset. This matters because safe-area insets only have a nonzero value at all when the page opts into edge-to-edge rendering via the viewport meta tag’s viewport-fit=cover; without it, the browser already keeps content clear of the notch for you and the insets report zero.
Why this needs a CSS-level solution
Before env(), working around a device notch meant either shipping fixed pixel values that only matched one family of devices, or reaching into JavaScript to measure the safe area at runtime and write the result back into the DOM as inline styles. Both approaches were fragile: hardcoded values broke on the next device generation, and JavaScript-computed insets couldn’t respond directly to things like orientation changes without extra event listeners and reflows. env() solves this the way CSS solves most layout problems — declaratively, updated automatically by the browser whenever the actual safe area changes, no JavaScript required.
Combining env() with calc() and clamp()
Because env() returns a length, it composes with calc() and clamp() exactly like any other length value. A common pattern adds a fixed margin on top of the platform-reported inset, so content clears the notch with some breathing room to spare rather than butting right up against it:
.toolbar {
padding-top: calc(env(safe-area-inset-top) + 12px);
padding-bottom: calc(env(safe-area-inset-bottom) + 12px);
}
This is especially relevant for fixed-position elements — bottom navigation bars, floating action buttons, sticky headers — since those are exactly the elements likely to sit under a notch or home indicator if their padding doesn’t account for the safe area. A full-screen modal or a bottom sheet is another common case: without the inset, its close button or primary action can end up drawn partly behind the home indicator bar, tappable in theory but visually cramped against the edge of the screen in a way that reads as sloppy on a device it wasn’t tested on.
Testing without the physical device
Not every developer has a drawer full of notched phones to test against, which makes this feature easy to get wrong silently. Browser developer tools that emulate specific devices typically also emulate their reported safe-area insets, so toggling device emulation is usually enough to see the padding behave as it would on real hardware. It’s worth testing at least one device with a top notch and one with a bottom home indicator, since a layout that only accounts for one can still clip content against the other.
Beyond safe-area insets
The env() specification defines other environment variables beyond safe-area insets, though safe-area is by far the most widely used in practice today. The mechanism itself is intentionally open-ended: it exists as a general channel for the platform to expose facts about the viewing environment that no stylesheet author could hardcode reliably, in the same spirit as container queries exposing facts about an element’s container rather than the viewport.
Where it fits with logical properties
Safe-area insets are physical — top, right, bottom, left — which is a slight mismatch with CSS logical properties, which describe direction relative to writing mode (padding-block-start rather than padding-top). When building layouts that need to support both safe areas and non-LTR writing modes, you typically still reach for the physical env() inset names, since they correspond to actual device geometry rather than text direction — a rare case where physical properties remain the more correct choice even in a codebase that otherwise standardizes on logical ones.
The takeaway
env() gives CSS a way to read values the platform defines, most usefully the four safe-area insets that describe how much space notches, camera cutouts, and home indicators occupy on a given device. Pair it with a fallback value and calc() to add your own margin on top of the platform’s reported inset, and enable viewport-fit=cover in the viewport meta tag so the insets actually report something other than zero. It’s a small feature, but for edge-to-edge layouts on modern phones, one that saves you from hardcoding device dimensions that will be obsolete within a year.
Tagged
Keep reading
Takina · · 4 min read CSS object-fit and object-position, Explained
object-fit controls how an image or video is cropped inside its box, and object-position controls which part of it stays visible. How they work together.
Takina · · 5 min read CSS inherit, initial, unset & revert Explained
CSS's four global keywords control where a property's value comes from. How inherit, initial, unset, and revert differ, with a comparison table.
Takina · · 4 min read Dynamic Viewport Units: dvh, svh, and lvh Explained
dvh, svh, and lvh fix the classic mobile vh bug where browser toolbars cut off full-height layouts. Here's what each unit measures and when to use it.