Marketplace layout: 320px sideways scroll, a dead sticky header, and a 100vh hero
A marketplace product page uses <div class="layout"><aside class="filters">…</aside><main class="grid">…</main></div> with .layout{display:flex;gap:24px}, .filters{flex:0 0 260px}, .grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:16px;padding:0 16px} and .card{min-width:280px}. On a 320px-wide phone the document scrolls sideways by about 40px, and one card's SKU string (SKU-8842-XL-RED-COTTON-LONG) runs out of the card. The site header uses position:sticky;top:0 and stopped sticking the day a teammate added overflow-x:hidden to .layout. The hero uses height:100vh, and on iOS Safari its call-to-action button hides behind the collapsing toolbar until the user scrolls. Lighthouse flags tap targets at 28×28 px and a colour-contrast ratio of 3.9:1 on the filter chips. The team's fix so far has been body{overflow-x:hidden}, which hides the symptom on Android but not on iOS.
1. Identify at least three independent causes of the horizontal overflow and give the specific fix for each. Explain why `body{overflow-x:hidden}` is the wrong response.
Read the worked answer
Several things are each capable of producing overflow, so fix them one at a time and re-measure with document.documentElement.scrollWidth versus clientWidth at 320px.
- The sidebar does not collapse.
.layoutis a flex row with a fixed 260px sidebar, 24px gap, and a grid padded by 16px each side: 260 + 24 + 280 is far beyond 320. Defaultflex-wrapisnowrap, so the row cannot break. Fix:@media (max-width: 48rem){ .layout{flex-direction:column} .filters{flex:1 1 auto;width:auto} }, or replace the whole layout withgrid-template-columns: 260px minmax(0,1fr)and a single-column media query. - Grid and flex items refuse to shrink. The automatic minimum size of a grid/flex item is its min-content size, so the unbreakable SKU forces the track wider than the viewport.
.card{min-width:280px}makes it worse by overriding the only lever you had. Fix: drop the hardmin-width, addmin-width:0on the grid item, and.card .sku{overflow-wrap:anywhere}(orword-break:break-word) so the string can wrap. - Track sizing below the minimum.
minmax(280px, 1fr)yields one 280px track; with the grid's 32px of padding that is 312px, which fits 320px but not a 300px viewport or a scrollbar. Userepeat(auto-fill, minmax(min(280px, 100%), 1fr))so the track can never exceed the container.
overflow-x:hidden is wrong because it clips content instead of reflowing it: data becomes unreachable, it can silently break position:sticky descendants, and on iOS it often just moves the scroll to the nearest ancestor. Use it only as a temporary diagnostic, never as the fix - and confirm with the scrollWidth measurement rather than by eye.
2. The sticky header stopped working. Explain the mechanism, give two viable fixes, and address the `100vh` hero at the same time.
Read the worked answer
A sticky element sticks within its nearest scrolling ancestor. Any ancestor with overflow other than visible - hidden, auto, scroll, and importantly clip - becomes that scrolling box, and since the clipped ancestor does not itself scroll, the header never moves and appears static. When overflow-x:hidden was added to .layout, the browser computed overflow-y as auto (a specified visible is converted to auto when the other axis is not visible), so .layout became the scroll container and the sticky context was destroyed.
Fixes: (a) remove the overflow declaration and solve the actual overflow with the reflow fixes above - always the first choice; (b) if a clip is genuinely needed for decorative bleed, use overflow: clip on a wrapper that is not an ancestor of the sticky element, and keep the header outside any transformed or filtered container too, since transform, filter, perspective, and contain:paint create containing blocks that also break sticky.
For the hero, 100vh on iOS Safari equals the largest viewport height, including the space the collapsing toolbar will occupy, so content pinned to the bottom of a 100vh box starts hidden. Use min-height: 100svh for a hero that must be fully visible on load, 100dvh for something that should track the toolbar, and always keep a plain vh declaration first as the fallback for older engines. Also raise the tap targets to at least 24×24 CSS px (44×44 recommended) by increasing padding rather than by scaling the whole chip, and darken the chip text/background pair until it clears 4.5:1 for normal-size text.
3. The same card component appears in the 260px sidebar (compact) and the 900px main grid (full). The team currently switches card layout with viewport media queries. Explain the failure mode and how you would restructure with container queries, including the fallback and the trade-offs.
Read the worked answer
Viewport media queries describe the page, but the card's problem is its own width. A 900px viewport can contain a 240px sidebar card, so a @media (min-width: 48rem){ .card{grid-template-columns: 120px 1fr} } rule will make the sidebar card try to lay out as a wide card in a narrow box - the exact bug the team keeps patching with !important overrides per context.
Restructure:
.card-host { container-type: inline-size; container-name: card; }
.card { display: grid; gap: 8px; }
@container card (min-width: 30rem) {
.card { grid-template-columns: 9rem minmax(0, 1fr); align-items: start; }
.card img { aspect-ratio: 1; }
}
The card then adapts to whichever column it is placed in, and the same component works in a future third context without new CSS. inline-size containment is the cheap axis: it does not require the element to be sized in both dimensions, but it does mean the container's width can no longer depend on its contents, so the host element needs a definite width - in a grid track that is already true.
Fallbacks and trade-offs: container queries are supported in all current evergreen browsers but not in older Safari 15 and some embedded webviews. Write the media-query version first as a progressive-enhancement baseline, then override inside @container, or gate with @supports (container-type: inline-size). Note that @container rules cannot style the container itself, only descendants - so keep the host element as a plain wrapper. Testing cost is real: your visual regression suite must now render the card in both contexts, which is exactly the coverage that was missing.