Bonbon Dino — Mobile Responsiveness Checklist¶
These are best practices and references, not hard requirements. Mobile breakage is a recurring problem across projects: oversized headers, text that runs off the screen, buttons that wrap into ugly stacks. This checklist captures the patterns that prevent it. Check every page at 375px wide (iPhone SE) before calling a build done — that is the narrowest common phone and where things break first.
The patterns assume an inline-style or Tailwind frontend. Translate the idea, not the exact syntax.
Companion files¶
website-checklist.md— general frontend marketing-site patterns (header, hero, SEO)admin-panel-checklist.md— auth, dashboard, settingsdebugging-lessons.md— symptom → root cause → fix logRETROSPECTIVE.md— session-end ritual: review what was learned, propose appends
0. The 375px rule¶
What it does: Every page is checked at 375px width (iPhone SE) in browser dev tools before merge.
Why: Most layout bugs only appear on narrow screens. A page that looks fine at 1440px can have a header eating half the screen and paragraphs clipped off the right edge at 375px.
How to check: Open dev tools → device toolbar → set to 375 × 667. Scroll the whole page. Look for: horizontal scrollbar, text touching or past the right edge, elements overlapping, a header taller than ~64px.
Quick test for hidden overflow (paste in console):
[...document.querySelectorAll('*')].filter(el => el.scrollWidth > document.documentElement.clientWidth).slice(0,20)
1. Header collapses to a hamburger on mobile¶
What it does: Below ~768px the header shows only the logo + a hamburger button. Tapping it opens a dropdown with the nav links, language toggle, admin, and the primary CTA. The desktop row of buttons is hidden.
Why: A logo, wordmark, nav link, language toggle, lock button, and CTA cannot fit one line at 375px. Without a hamburger they wrap into a tall, messy block or overflow. This was the exact bug on the Bonbon Dino landing + contact pages (2026-06).
How to implement: Reuse the existing useIsMobile() hook (src/hooks/use-mobile.tsx, breakpoint 768px). Render the desktop cluster when !isMobile, a hamburger button when isMobile, and a position: absolute; top: 100% dropdown panel when isMobile && menuOpen.
const isMobile = useIsMobile();
const [menuOpen, setMenuOpen] = useState(false);
// ...
{isMobile ? <HamburgerButton onClick={() => setMenuOpen(o => !o)} /> : <DesktopNav />}
{isMobile && menuOpen && <MobileMenuPanel />}
Gotchas:
- Close the menu (setMenuOpen(false)) on every nav action — scroll-to-section, CTA link click, opening the admin modal — or it stays open over the content.
- Give the dropdown a high zIndex and an opaque/blurred background so page content does not show through.
2. Logo and wordmark scale with the viewport¶
What it does: The logo image and brand wordmark shrink on small screens instead of staying at their desktop pixel size.
Why: A fixed width: 176px logo is 47% of a 375px screen. Combined with a fontSize: 40 wordmark it alone overflows the header.
How to implement: Use clamp(min, vw, max) for width and font-size so they scale fluidly.
width: clamp(92px, 13vw, 176px); height: auto;
fontSize: clamp(24px, 4.8vw, 40px);
height: auto (not a fixed pixel height) so the image keeps its aspect ratio as the width shrinks. If the logo uses negative margins to crop PNG whitespace, clamp those too, or they over-crop at small sizes.
3. Never use white-space: nowrap on body text¶
What it does: Paragraphs and descriptions are allowed to wrap.
Why: whiteSpace: "nowrap" forces text onto one unbreakable line. At 375px a 90-character sentence runs straight off the right edge and gets clipped — invisible to the user. This was the "Services" section cutoff bug (2026-06).
Rule: nowrap is only ever acceptable on short, atomic things: a button label, a single nav item, a stat number, a 2-word highlighted phrase. Never on a sentence or paragraph. When in doubt, leave it off.
Also: give long text a sane maxWidth (e.g. 560px) so lines do not get uncomfortably wide on desktop while still wrapping on mobile.
4. Fluid font sizes everywhere¶
What it does: Text scales between a mobile-friendly minimum and a desktop maximum.
Why: A heading fixed at 40px is fine on desktop and cramped/overflowing on a phone. Mixing fixed and fluid sizes is how one section ends up broken while the rest of the page is fine.
How to implement: Prefer clamp(minPx, vw, maxPx) over a bare pixel value for any text above ~16px. Audit for stragglers:
grep -rEn 'fontSize: *[2-9][0-9]([^v]|,|$)' src/pages/ # fixed sizes >=20px, candidates to make fluid
5. Flex rows wrap and have minWidth floors¶
What it does: Multi-column flex layouts (hero, card grids, button rows) stack vertically on narrow screens.
Why: Two 50% columns side by side become unreadable slivers at 375px.
How to implement: Add flexWrap: "wrap" to the row and minWidth (e.g. 280–300px) plus flex: "1 1 <basis>" to each child so it drops to a full-width row when it can't fit. Set overflowX: "hidden" on the page root as a backstop, but treat any real overflow as a bug to fix at the source, not hide.
6. Avoid zoom as a mobile fix¶
What it does: Do not use CSS zoom: 0.9 (or similar) on a page root to "make everything fit."
Why: zoom uniformly shrinks the desktop layout instead of adapting it. It masks real responsive failures, behaves inconsistently across browsers, and throws off vw/clamp math. Fix the layout responsively instead.
Real symptom (2026-06): zoom: 0.9 on the landing-page root made the footer unreachable on iPad Pro Safari — the footer rendered but sat below the scrollable height, so it never appeared. Safari's zoom support is partial and miscalculates scroll height; Chrome hid the bug. Detection: a footer whose offsetTop is greater than the document scrollHeight.
const f = document.querySelector("footer");
console.log(f.offsetTop, document.documentElement.scrollHeight); // offsetTop must be <= scrollHeight
zoom and let the responsive units carry the layout.
7. Tap targets and inputs¶
What it does: Interactive elements are big enough to tap and inputs don't trigger zoom.
Why: Small tap targets are frustrating on touch; iOS Safari auto-zooms when you focus an input whose font-size is under 16px.
How to implement: Minimum ~40×40px touch target for icon buttons (the hamburger, the lock). Form <input>/<textarea> font-size ≥ 16px. Generous vertical padding on menu items in the mobile dropdown.
Mobile QA pass (run before every merge)¶
- [ ] Every page viewed at 375px — no horizontal scroll, no clipped text
- [ ] Console overflow snippet (§0) returns nothing
- [ ] Header is a hamburger on mobile; menu opens, all items work, menu closes on action
- [ ] Logo/wordmark scaled down, not desktop-size
- [ ] No
white-space: nowrapon any sentence or paragraph - [ ] Headings/body use
clamp(), not bare large pixel sizes - [ ] Flex rows stack instead of squeezing
- [ ] No
zoomhack on the root - [ ] Footer is reachable at the bottom on tablet (iPad Pro) —
footer.offsetTop <= scrollHeight - [ ] Icon buttons ≥ 40px; inputs ≥ 16px font
- [ ] Checked both EN and KO — Korean text wraps differently and can overflow where English fits
How to extend this file¶
When a new mobile bug bites, add or rewrite the relevant section above (don't append a duplicate). Record the symptom, the root cause, and the rule that prevents it. Keep examples short. If a "Fix" here goes stale, rewrite it in place — outdated guidance is worse than none.