Source Code

<div class="demo">
  <div class="phone-frame">
    <div class="notch"></div>
    <div class="product-screen">
      <div class="product-scroll">
        <div class="product-hero">Wireless Headphones</div>
        <p class="product-desc">Active noise cancellation, 30-hour battery life, and a genuinely comfortable fit for long listening sessions.</p>
        <p class="product-desc">Scroll down — the price and Add to Cart bar stays pinned above the home-indicator area at the very bottom of the screen, never overlapping it.</p>
        <div class="product-spacer"></div>
      </div>

      <div class="cta-bar">
        <div class="cta-price">
          <span class="cta-price-amount">$179</span>
          <span class="cta-price-was">$229</span>
        </div>
        <button class="cta-btn">Add to cart</button>
      </div>

      <div class="home-indicator"></div>
    </div>
  </div>
</div>

Sticky Footer CTA Bar with Safe-Area Insets — Never Overlapped by the Home Indicator

Sticky Footer CTA Bar with Safe-Area Insets · Mobile · Plain HTML, CSS & JS · Live preview

What's included

Features

Uses env(safe-area-inset-bottom) to correctly clear the home-indicator gesture area on notched devices
Base padding and the safe-area inset are additive via calc(), preserving intentional visual spacing on every device
Explicit 0px fallback inside env() keeps the calc() expression valid even on browsers without safe-area support
Automatically collapses to plain base padding with zero extra inset on devices without a reserved home-indicator area
No JavaScript or device detection required — the correct behavior is entirely native CSS
Bar remains sticky and fully interactive while the page content above it scrolls independently
Documents the required viewport-fit=cover meta tag needed for real deployment on notched devices

About this UI Snippet

Safe-Area-Aware Sticky CTA Bar — Correct on Every Device, With One CSS Function

Screenshot of the Sticky Footer CTA Bar with Safe-Area Insets snippet rendered live

A sticky bottom action bar is one of the most common mobile e-commerce patterns — price on the left, "Add to cart" on the right, always visible while scrolling. But on modern phones with no physical home button, the operating system reserves a strip of space at the very bottom of the screen for its swipe-up home gesture — and a bar with ordinary fixed padding can render its button uncomfortably close to (or even overlapping) that gesture area. This snippet fixes it with a single CSS function: env(safe-area-inset-bottom).

What `env(safe-area-inset-bottom)` actually returns

On a device with a home-indicator gesture area (most modern iPhones, and many modern Android phones), the browser exposes the height of that reserved area through this environment variable — a real, device-reported pixel value. On a device *without* such a reserved area (an older phone, or a phone with a physical home button), the same environment variable simply evaluates to 0px. This is what makes it possible to write one CSS rule that's correct on every device, without any JavaScript device-detection or user-agent sniffing.

Why `calc(14px + env(...))` instead of `env()` alone

The bar's bottom padding is written as calc(14px + env(safe-area-inset-bottom, 0px)) — the base 14px is the bar's normal, intentional visual padding (present on every device, safe area or not), and the environment variable is *added* on top of that as extra device-specific clearance, not used as a replacement for it. Using env() alone would mean a device with a large safe-area inset gets *only* that inset as padding (potentially with no visual breathing room at all if the inset happens to be small), while a device with a zero inset would get literally zero padding around the button — neither is the intended design.

The fallback value inside `env()` matters for older browsers

env(safe-area-inset-bottom, 0px)'s second argument is a fallback used by browsers that don't recognize the safe-area-inset-bottom keyword at all (rather than recognizing it and returning zero) — without an explicit fallback, an unsupporting browser could treat the whole env() call as invalid, potentially breaking the calc() expression it's nested inside. Providing 0px explicitly keeps the calculation valid and predictable everywhere, supporting or not.

Why this requires the viewport meta tag's `viewport-fit=cover` in a real deployment

For env(safe-area-inset-*) values to be non-zero at all (rather than always reporting 0 even on a notched device), the page's <meta name="viewport"> tag needs viewport-fit=cover — this tells the browser the page wants to render edge-to-edge, including into the safe-area-adjacent regions, which is what makes the safe-area inset values become meaningful and non-zero in the first place. Without it, the page renders letterboxed within the safe area automatically, making the env() values simply always zero since there'd be nothing to inset around.

Build with AI

Build, Understand, Optimize, and Extend It With AI

Ask an AI assistant to explain exactly what viewport-fit=cover changes about how a page renders on a notched device, and why it's a prerequisite for env(safe-area-inset-*) values to become non-zero. It's also worth asking for a version that applies the same safe-area-aware padding pattern to a fixed top header (using safe-area-inset-top) and a fixed side element on a landscape-oriented notched device (using safe-area-inset-left/right), covering all four possible inset directions.

Prompt to recreate it

Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:

text
Build a sticky bottom call-to-action bar for a mobile product page in HTML and CSS, correctly handling device safe-area insets — minimal or no JavaScript needed beyond a small interactive demo touch.

Requirements:
- A mobile page layout with a scrollable content area and a sticky bar fixed to the bottom of the screen, containing a price display and an "Add to cart" button.
- The bar's bottom padding must use CSS's env(safe-area-inset-bottom) environment variable, added on top of a normal intentional base padding value via calc() — not used as a replacement for that base padding, so the bar has sensible visual spacing on every device regardless of whether it has a safe-area inset or not.
- Provide an explicit fallback value (0px) as the second argument to env(), so the calc() expression remains valid on browsers that don't recognize the safe-area-inset-bottom keyword at all.
- Include a comment or visual note explaining that a real deployment of this pattern requires <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover"> in the page head for the safe-area inset values to report non-zero on supporting devices.
- Include a small visual mock of a device home-indicator pill near the bottom of the screen so the safe-area clearance concept is visually demonstrated even in a browser preview without a real device inset.
- Make the Add to cart button show a brief success confirmation state when clicked, to demonstrate the bar remains fully interactive.

Want to tighten it up first? Run this prompt through the AI Prompt Studio to score it across 8 quality dimensions, catch anti-patterns, and tune the wording for Claude, ChatGPT, or Gemini before you paste it in.

Step by step

How to Use

  1. 1
    Scroll the product screenThe price-and-action bar stays pinned to the bottom of the screen at all times, positioned above a simulated home-indicator pill.
  2. 2
    Notice the extra bottom paddingThe bar's padding-bottom is calc(14px + env(safe-area-inset-bottom, 0px)) — on a real notched device this adds genuine extra clearance; in this browser preview it still shows the base 14px.
  3. 3
    Click "Add to cart"A small success state confirms the bar's content remains fully interactive and unobstructed regardless of the extra safe-area padding.
  4. 4
    Deploy with viewport-fit=coverAdd <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover"> to your real page's <head> — this is required for env(safe-area-inset-*) to report non-zero values on supporting devices.
  5. 5
    Reuse the calc() pattern on other fixed elementsAny element pinned to the bottom (a fixed nav bar, a cookie banner, a floating action button) can use the same calc(base + env(safe-area-inset-bottom, 0px)) pattern for its bottom spacing.

Real-world uses

Common Use Cases

ECOMMERCE
Product detail page action bars
Price and Add-to-cart bars are one of the most common places this exact bug (overlap with the home indicator) shows up on real e-commerce sites.
Any fixed bottom navigation or action bar
Bottom tab bars, floating action buttons, and persistent CTA bars all need the same safe-area-aware padding treatment.
Mobile checkout continue/submit bars
A fixed "Continue" button bar at the bottom of a multi-step mobile checkout flow needs to clear the gesture area just as much as a shopping cart bar.
PWA
Installed PWAs and full-screen web apps
Progressive web apps running full-screen (no browser chrome) are exactly where safe-area handling matters most, since there's no browser UI to naturally provide the clearance instead.
Related: Multi-Touch Gesture Visualizer
See the Multi-Touch Gesture Visualizer for a related mobile pattern worth pairing with this one.

Got questions?

Frequently Asked Questions

On a device with a reserved home-indicator gesture area (most modern iPhones and many modern Android phones), it returns the real height of that area in pixels. On a device without one, it evaluates to 0px — the same CSS works correctly on both without any device detection.

Using it alone would mean the bar's padding is ENTIRELY determined by the device's inset — a device with no inset (evaluating to 0px) would get zero visual padding around the button at all. Adding it on top of an intentional base padding value (via calc()) preserves normal visual breathing room on every device while adding device-specific extra clearance only where actually needed.

It's an explicit fallback value used by browsers that don't recognize the safe-area-inset-bottom environment variable at all. Without it, an unsupporting browser could treat the entire env() call as invalid, which would break the surrounding calc() expression rather than gracefully falling back to zero.

A browser preview (like an iframe embedded in a desktop page) typically isn't running as a true full-screen mobile context with a real safe-area inset, so env(safe-area-inset-bottom) evaluates to 0px here — on an actual notched device rendered full-screen with the correct viewport meta tag, the same CSS produces genuinely extra bottom padding.

It's a value for the viewport meta tag's content attribute that tells the browser the page wants to render edge-to-edge into the safe-area-adjacent regions of the screen. Without it, most browsers render the page letterboxed within the safe area automatically, which makes every env(safe-area-inset-*) value simply report zero since there'd be no inset region to account for.

Yes — env(safe-area-inset-top) works identically for elements fixed to the top of the screen, using the same calc(base + env(safe-area-inset-top, 0px)) pattern for top padding instead of bottom.