A step-by-step walkthrough of how a realistic iPhone frame is built from scratch — bevel gradients, layered shadows, glass shine effects, and mouse-reactive 3D tilt.
A phone mockup looks like one effect, but it's actually six composited layers — each doing a specific job. Collapse them all and you get a flat coloured rectangle. Stack them in the right order and the eye reads it as glass, metal, and depth. This is a walkthrough of how I built the PhoneMockup component above, from a blank div to a mouse-reactive 3D frame. We'll add one layer at a time so it's clear what each one contributes.
Layers 5 and 6 are JavaScript: tilt physics and reactive shadows. Let's build up to them. The gradient runs from #555 to #000 at 135° — top-left lighter than bottom-right. This simulates a brushed aluminium edge catching light from above. It's one CSS property doing optical work that would otherwise take a texture. The borderRadius is calculated as width * 0.12. The iPhone's corner radius scales proportionally to the device width, so we keep it as a ratio rather than a fixed pixel value.
Resize the component and the corners stay iPhone-shaped. 5px of padding is the visible dark frame around the screen. Notice borderRadius here is outerRadius, not outerRadius + 1 — the outer wrapper gets +1 to avoid a visual gap between the bevel ring and the body where the gradient might bleed through. innerRadius is outerRadius – 5 — matching the 5px padding offset. This keeps the inner corners visually concentric with the outer ones.
Without this, the screen corners appear too sharp or too round relative to the frame. A single box-shadow looks artificial — the falloff is a crisp edge rather than a natural penumbra. We stack nine layers, each progressively lighter and more diffuse: Each step doubles the blur, adds more spread, and halves the opacity. The result is a soft, real-looking shadow that falls off the way light actually does.
This is passed as the box-shadow property alongside the edge shadows we'll add in Layer 6. overflow: hidden does the border-radius clipping on the image — the image itself doesn't need a borderRadius because the container clips it. lineHeight: 0 is non-obvious but essential. Images are inline elements by default, which means the browser reserves space below them for text descenders — about 4px of empty space at the bottom of the container.
Setting lineHeight: 0 on the wrapper collapses that. Without it, the image floats slightly above the bottom of the frame. We need to maintain the real iPhone aspect ratio. The iPhone 17 Pro is 2622 × 1206 pixels — a ratio of roughly 2.175:1. We derive height from width: Pass a width prop and height follows automatically. Hardcoding a height would break the proportions the moment someone changes the width.
The inset shadow overlay sits at zIndex: 2 above the image. It's a dark ring that recesses the screen into the bezel — the difference between a screen that looks flush and one that looks embedded. borderRadius: 999 on the island container is intentional. A value large enough — larger than half the element's height — always produces a perfect pill shape regardless of the element's actual dimensions.
No need to recalculate as width changes. The radial gradients are positioned off-center (35% 35%, 40% 40%) to simulate a fixed light source above and to the left. Moving the gradient center away from 50% 50% shifts the bright spot, making the glass appear to have a real angle relative to the light. The zIndex: 1 on the island positions it above the image but below the shine overlays (zIndex: 2). The Dynamic Island is part of the device, not a reflection.
The trick here is the negative positioning. Setting top/left/right/bottom all to -25% makes this div 50% larger than its parent in every direction. The parent has overflow: hidden, so only the portion that falls within the screen bounds is visible. Why bother making it oversized? So we can rotate and translate it without revealing transparent corners. When the tilt physics move this overlay (Layer 6), it needs room to slide without the edge becoming visible.
The gradient has two colour stops at the same percentage: rgba(255,255,255,0.08) 35% and rgba(255,255,255,0) 35%. Same position, different values — that's how you get a hard edge in a CSS gradient. No transition, just a sharp cut. Gentler falloff, slightly different angle. This adds the ambient diffusion that sits behind the glint — making the top-left corner of the screen look softly lit rather than just adding one bright stripe.
getBoundingClientRect() returns the element's position in the viewport. Subtracting rect.left converts from viewport-absolute to element-local coordinates. Dividing by rect.width normalises to 0–1. Multiplying by 2 and subtracting 1 shifts to −1..1. rotateX: -y is the axis inversion. When the pointer is near the top of the element, the top of the phone should tilt toward you — that's a positive rotateX in CSS 3D space.
But y is negative at the top (above centre), so we negate it. perspective(800px) is the simulated camera distance. Smaller values increase the foreshortening effect — 400px looks dramatic, 1200px looks nearly flat. 800px hits the sweet spot for a phone-sized element. transformStyle: "preserve-3d" propagates the 3D context to child elements. Without it, all the children collapse to the same plane and depth ordering breaks.
willChange: "transform" is a GPU hint — promotes the element to its own compositing layer before any animation starts, avoiding a repaint on first move. e.touches[0] is the first touch point. clientX and clientY are the same coordinate space as mouse events, so tiltFromPoint handles both. When you tilt a physical phone, you see its edges. We fake this with offset box-shadow: When rotateY > 0 (tilting right), edgeX is negative, shifting the shadow left — which makes the left edge appear to protrude.
Two stacked layers (EDGE_LAYERS = 2) with progressive offsets give soft depth rather than one sharp step. The blur radius is 0 — these are solid-colour shadows, not diffuse. They're edge geometry, not light scatter. lightAlign measures how much the screen's normal vector aligns with the fixed top-left light source. Tilt the top-left corner toward the viewer and lightAlign increases, brightening the shine.
Tilt it away and the shine fades. shineX/Y slide the oversized shine overlay in the direction opposite to tilt — the reflection appears to hold still relative to the light source while the glass moves beneath it. At rest it's at top: 20%, left: 25% — off-center, simulating a fixed light above-left. As the device tilts, it moves, maintaining the illusion of a consistent light source. We listen for runtime changes, not just the initial value — users can toggle system preferences while the page is open.
When reducedMotion is true, tiltFromPoint returns early. The transition CSS is still present but never fires. The key insight: physical objects fool the eye by accumulating subtle cues. A single gradient isn't convincing. A single shadow isn't convincing. But six layers, each doing their specific job — bevel, shadow falloff, depth ring, glass glint, edge geometry, reactive shine — combine into something the brain reads as solid.
The mockup was missing one detail that real phones have — hardware buttons on the sides. Three on the left (action button, volume up, volume down) and a power button on the right. The buttons are absolutely positioned inside the perspective container, sitting just outside the bezel edge. Each one is a thin rectangle with a metallic gradient, rounded on its outward-facing corners: Positions and heights are expressed as fractions of the total phone height, so they scale proportionally with the width prop.
At the default 218px width, each button is about 3px wide — subtle but visible. The interesting part: the buttons react to tilt. On a real phone, you only see the side buttons when the edge faces you. Here, each button's opacity is tied to the tilt direction that reveals its side: Base opacity is 0.3 so they're always faintly visible, rising to 0.7 at full tilt. The transition follows the same 300ms ease-out as the rest of the tilt system, so it feels physically connected.
Summary
This report covers the latest developments in iphone. The information presented highlights key changes and updates that are relevant to those following this topic.
Original Source: Conor.fyi | Author: Conor Luddy | Published: March 12, 2026, 9:23 am


Leave a Reply
You must be logged in to post a comment.