A pure-CSS iPhone 16 Pro with a realistic iOS 26 "Liquid Glass" Safari shell. Titanium frame, Dynamic Island, protruding buttons — and Safari's chrome in every layout (Compact, Bottom, Top) plus standalone PWA, each with its correct safe-area insets. Swap one class to change the mode. Drop your page into the screen. No build step, no dependencies.
Plenty of device mockups draw a nice iPhone. Almost none draw the browser chrome correctly — and on iOS 26 that chrome is the hard part: the frosted "Liquid Glass" URL pill, the three different Safari layouts, and the fact that each layout reports different safe-area insets that push your content around.
liquidframe reproduces all of it in plain CSS so you can show a web page exactly as it appears on a real iPhone — in screenshots, marketing pages, design reviews, docs, or a library demo.
Copy liquidframe.css (and optionally liquidframe.js) into your project, then:
<link rel="stylesheet" href="liquidframe.css">
<div class="phone-frame chrome-compact">
<div class="phone-btn phone-btn-action"></div>
<div class="phone-btn phone-btn-volup"></div>
<div class="phone-btn phone-btn-voldown"></div>
<div class="phone-btn phone-btn-power"></div>
<div class="phone-notch"></div>
<div class="phone-viewport">
<div class="status-bar-info"><div><span data-lf-clock>9:41</span></div><div><!-- icons --></div></div>
<!-- Safari chrome (all three included; CSS shows the right one per mode) -->
<!-- copy these blocks from index.html -->
<div class="home-indicator-area"></div>
<div class="phone-screen">
<!-- 👇 YOUR PAGE GOES HERE -->
</div>
</div>
</div>The fastest path is to open index.html, view source, and copy the whole .phone-frame block — the Safari chrome markup (status bar icons, URL pills, button rows) is verbose and already wired up there.
Change the mode by swapping one class on .phone-frame. No JavaScript needed.
| Class | iOS layout | What shows |
|---|---|---|
chrome-compact |
Compact (default) | 3-capsule bottom bar: [◁] [▢ url ↻] [···] |
chrome-bottom |
Bottom | One card: URL row + buttons row |
chrome-top |
Top | URL pill up top, slim buttons bar at the bottom |
chrome-pwa |
Standalone PWA | No Safari chrome, minimal insets |
Each mode also sets the correct --sim-safe-top / --sim-safe-bottom insets, so content padded with var(--sim-safe-bottom) reflows just like on a real device.
Set these CSS variables on .phone-frame (or :root):
| Variable | Default | Purpose |
|---|---|---|
--screen-bg |
#ffffff |
Backdrop shown in the rubber-band / overscroll area |
--chrome-top |
transparent |
Tint painted behind the status bar |
--chrome-bot |
transparent |
Tint painted in the bottom safe-area band |
--status-fg |
#fff |
Status bar text / icon color |
<div class="phone-frame chrome-compact" style="--chrome-top:#0a8c8e; --chrome-bot:#0a8c8e; --status-fg:#fff;">Tinting
--chrome-top/--chrome-botis exactly how you'd demo a chrome-tinting library, or preview a themed app.
Add a finish class to .phone-frame (no class = Natural Titanium):
frame-desert · frame-black · frame-white
The mockup is fully functional with zero JS. liquidframe.js only adds conveniences:
import { enhance, setChromeMode, setTitanium } from './liquidframe.js';
enhance(); // live clock, wheel-scroll, scroll-prompt fade
setChromeMode(phoneEl, 'top'); // swap chrome mode from your own UI
setTitanium(phoneEl, 'black'); // swap titanium finishenhance(root?)— starts a live clock on any[data-lf-clock]element, redirects desktop wheel scrolling into the screen, and fades any.scroll-prompt-overlay.- Loaded as a plain
<script>, it auto-runsenhance()and exposeswindow.liquidframe.
.phone-frame.chrome-compact ← mode + titanium classes live here
├── .phone-btn ×4 physical side buttons
├── .phone-notch Dynamic Island
└── .phone-viewport
├── .status-bar-info clock + signal/wifi/battery
├── .sim-safari-top-capsule Safari "Top" URL pill
├── .sim-safari-bottom-liquid-bar Safari "Bottom" bar
├── .sim-safari-compact-bar Safari "Compact" 3-capsule bar
├── .home-indicator-area bottom safe-area band
└── .phone-screen ← your scrollable content
The screen is a container named phone, so content can size to the viewport with min-height: 100cqh.
Works anywhere backdrop-filter and CSS container queries are supported — every current evergreen browser (Chrome, Safari, Firefox, Edge). It renders identically on desktop and mobile; it is a visual mockup, not a behavioral emulator.
Continuous-curvature corners (corner-shape) are a progressive enhancement. The frame and screen draw Apple's true squircle via corner-shape: squircle, which only the newest engines support (Chrome/Edge 139+). Every browser that doesn't recognize it simply renders the border-radius circular-arc corner instead — same size, slightly rounder. Nothing breaks and there is no JavaScript fallback to load.
Optional JavaScript loaded as a plain <script> auto-runs enhance() exactly once — it waits for DOMContentLoaded if the document is still parsing, and runs immediately otherwise (e.g. when the tag is at the end of <body> or marked defer). Calling enhance() yourself again later is safe: each element is wired at most once, so there are no duplicate clocks or wheel handlers.
MIT