On a client site I was building, the hero used a stacked layout. Three absolute layers lived in the same container: a forest background at the bottom, a phone mockup image on top of it, then a few floating UI tags at the very top. The text itself, a headline plus bullets, was not absolute. It lived in normal flow, and I pushed it below the phone with a hand-computed padding-top so it would sit neatly under the phone frame. On desktop it was clean. On mobile it was clean too, at first.
Then client QA filed it: on mobile, the hero text was overlapping the phone image. The headline collided with the frame and only half of it was readable. I opened it locally at a 375px viewport, and sure enough, the phone had slid down until it stacked on top of the text. The odd part: this only appeared after I grew the hero content. The headline became 3 lines and the bullets became a 4-item grid. Before the content swelled, everything was fine.
The symptom
Here is what threw me off first: the text padding-top was an explicit number I computed myself, and on desktop it was pixel-precise. So my first instinct was bad, as usual. I assumed the padding was just too small, so I nudged it up. The overlap shrank, but a gaping gap opened up at other viewports. I fiddled with z-index, I suspected object-position, I even blamed the classic 100dvh Safari-mobile bug. All misses.
I stopped guessing and started measuring. I inspected the hero section, and the numbers spoke: a section whose min-height formula resolved to 940px was actually rendering at 1090px. The swollen content pushed the section 150px past its minimum. That is expected, the section is allowed to grow. What was not expected: the phone image slid down by almost exactly that same overflow.
The root cause
This is the part that made me stop for a second. All three absolute layers were stretched full height with top-0 bottom-0. The idea was that each layer would always cover the whole section. It looked safe. The problem: the phone layer and the tags layer grew right along with the section whenever it passed min-height. When the section became 1090px, the phone div became 1090px too.
Now, the phone image used object-cover object-center. And this is the trap: object-cover object-center always re-centers the image content inside its box. The moment the box grows, the image's center point shifts down with it. The phone frame that used to bottom out around 60 percent of the section height now bottomed out at 60 percent of a larger height.
Meanwhile the text padding-top was computed from the FIXED height formula, not from the swollen height:
/* globals.css, simplified */
.hero-section-min-h { min-height: max(100dvh, calc(64vh + 400px)); } /* allowed to grow past this */
.hero-content-pt { padding-top: calc((64vh + 400px) * 0.60 + 30px); }The 0.60 is the phone-bottom fraction of the section, and the +30px is breathing room below the phone. So the text was scheduled to start at 0.60 x 940 + 30 = 594px. But the real phone bottom, with its layer stretched to 1090px, landed at 0.60 x 1090 = 654px. Two numbers that were supposed to stay in sync drifted 60px apart. Text started at 594, phone ended at 654, so the top 60px of the text sat under the frame. Exactly what QA saw.
The bug was not in the padding value, and it was not z-index. The bug was that one side (the phone) was measured against a stretchable height while the other side (the text padding) was measured against a fixed height. As long as those two could differ, the overlap was just waiting for content long enough to trigger it.
The fix
The key was to make both sides share the same measuring base. The text uses a fixed height, so the phone and tags had to be locked to that same fixed height, not to the stretchable section height. I made one fixed-height class and applied it to the phone and tag layers. Only the background layer stays stretchable to cover the overflow:
.hero-layer-h { height: max(100dvh, calc(64vh + 400px)); } /* FIXED, does not stretch */Then I switched the layers from full-stretch to locked-height:
{/* BEFORE: layer stretches along with the section */}
<div className="absolute inset-x-0 top-0 bottom-0">
<img className="h-full w-full object-cover object-center" />
</div>
{/* AFTER: layer pinned to a fixed height */}
<div className="absolute inset-x-0 top-0 hero-layer-h">
<img className="h-full w-full object-cover object-center" />
</div>Now even when the section grows to 1090px, the phone div stays 940px. object-cover still re-centers, but inside a box whose height is constant, so the phone bottom stays at 564px, safely above the text starting at 594px. Bonus: because the phone and tags are both measured against the same layer height, the percentage-positioned tags stay locked relative to the phone, so tag-to-frame alignment does not shift at all.
One last detail. On mobile the bullet grid was no longer centered because it went full width. I centered it as a group with w-fit mx-auto, then restored left alignment on desktop:
<ul className="w-fit mx-auto xl:w-auto xl:mx-0">After that I verified across 375, 834, 1024, and 1440. The overlap was gone at every breakpoint and desktop stayed left-aligned as before.
Checklist
- If an
absolutelayer stretches withtop-0 bottom-0AND its content isobject-cover, remember the image re-centers every time the box grows. - Overlap shows up when one element is measured against a stretchable height and another against a fixed height. Make them share a base.
- Lock image layers to a fixed
height, and let only the background layer stretch to cover overflow. - Compute the text
padding-topfrom the exact same height formula that pins the image layers, not from the section that is allowed to grow. - If tags are positioned in percentages relative to the phone, locking both to the same height keeps their alignment for free.
- Test at several breakpoints with your longest content, not the short version. This overlap only appears when the section is forced to grow past
min-height.
