Blog · · 8 min

Mobile ecommerce UX: what breaks at 390 px

At 390 pixels a product page has roughly one screen to work with, and five things routinely eat it: an announcement bar, an oversized hero, a chat bubble, a cookie banner and a newsletter modal. Design the first screen at 390 first, then let it grow — the reverse never survives.

By

At 390 pixels wide, the first screen of a product page is about 640 logical pixels tall once the browser chrome is accounted for. Into that you have to fit the product, the price and the add button, and five common elements will take it from you before you notice:

  1. A sticky announcement bar — 40 to 56 px, permanently.
  2. A hero image sized by aspect ratio rather than by height — often 390 px tall on its own.
  3. A cookie banner covering the bottom third until dismissed.
  4. A chat bubble that opens itself after a delay.
  5. A newsletter modal on a timer.

Any two of those and the price is below the fold. Any three and the buyer's first interaction with your shop is dismissing something.

Why design at 390 and not at 1440?

Because a layout designed wide and then squeezed loses different things than a layout designed narrow and then expanded, and the things it loses are the important ones.

Designing wide, you place elements side by side, then at 390 they stack — and stacking is where the price ends up below the image, the size selector ends up below the price, and the button ends up below all of it. Nobody decided that order; it fell out of the source order of a two-column grid.

Designing narrow, you are forced to rank. Only one thing can be first. That ranking is the section order, and it turns out to be correct at every width, which is why the narrow-first version expands cleanly and the wide-first version does not compress cleanly.

What actually breaks, in order of cost

The tap target. Anything the buyer must hit is at least 44 px tall with 8 px of clear space around it. Variant swatches at 32 px in a tight row are the most common offender, and the failure is silent: the buyer taps the wrong colour, sees the wrong price, and leaves without reporting anything.

The number that jumps. A price that changes on variant selection, rendered in proportional digits, shifts sideways every time. It reads as a bug even to people who cannot say what is wrong.

/* Every figure that changes in place. Price, cart count, remaining amount. */
.price, .cart-count, .threshold-remaining {
  font-variant-numeric: tabular-nums;
}

The layout that shifts. An image without dimensions pushes the button down as it loads, and the buyer's thumb is already moving. Width and height on every image, always — this is the same reason the article renderer on this site refuses to emit an image whose dimensions it cannot read.

The keyboard that covers the field. On the checkout, a fixed footer with the pay button sits under the on-screen keyboard on iOS. Use the visual viewport rather than the layout viewport:

// The keyboard shrinks the VISUAL viewport, not the layout viewport.
// Without this, your sticky footer is behind the keyboard on iOS.
const vv = window.visualViewport;
if (vv) {
  const sync = () => {
    const hidden = window.innerHeight - vv.height - vv.offsetTop;
    document.documentElement.style.setProperty(
      "--keyboard-inset",
      `${Math.max(0, hidden)}px`,
    );
  };
  vv.addEventListener("resize", sync);
  vv.addEventListener("scroll", sync);
  sync();
}
.checkout-footer {
  position: fixed;
  bottom: 0;
  /* the home indicator AND the keyboard, in that order */
  padding-bottom: calc(env(safe-area-inset-bottom) + var(--keyboard-inset, 0px));
}

The horizontal scroll. One element wider than the viewport and the whole page rocks sideways. It is almost always a table, a code block, a wide image or a min-width left on a card. The fix is per-element overflow, never on the body.

What should the first screen contain?

Element390 px1440 px
Product imageone, tall enough to read the objectgallery beside the text
Product nameyesyes
Priceyes, next to the nameyes
Variant selectoryes, if fewer than about six optionsyes
Add to cartyesyes
Announcement baronly if it carries a real, dated offerfine
Reviews summaryone line, no widgetfine
Chat bubblenoafter interaction only
Newsletter modalnever on first screennever on first screen

The right column is permissive because there is room. The left column is not a reduced version of it; it is a different set of decisions.

![A narrow strip of dark slate photographed from directly above, a brass ruler laid across it marking 390, with three paper cards fitted end to end and a fourth card overhanging the edge into shadow.](/blog/regle-390.webp "One screen, three cards. The fourth is the announcement bar, the chat bubble or the modal — whichever one you added last is the one hanging over the edge.")

Where does the add button go when the buyer scrolls past it?

Into a sticky bar, and the bar has rules: it appears only after the inline button leaves the viewport, it carries the price, and it never covers content the buyer is reading. The full component and the failure modes are in sticky add to cart on mobile.

The detection is an observer on the inline button, not a scroll listener. A scroll listener runs on every frame and is the single most common cause of a page that feels heavy on a mid-range phone:

const [showSticky, setShowSticky] = useState(false);
const inlineRef = useRef<HTMLButtonElement>(null);

useEffect(() => {
  const el = inlineRef.current;
  if (!el) return;
  const io = new IntersectionObserver(
    ([entry]) => setShowSticky(!entry.isIntersecting),
    { rootMargin: "0px 0px -80px 0px" },
  );
  io.observe(el);
  return () => io.disconnect();
}, []);

What about the weight of the page?

It is the part nobody measures until paid traffic arrives, and then it is the first thing that shows up. A buyer on a throttled 4G connection who waits several seconds for a first paint is a buyer you paid for and never met.

The three that dominate on an agent-generated store, in order:

  • JavaScript that renders content that could have been static. A product page is documents, not an application.
  • Fonts loaded in more weights and scripts than the page uses. Subset to Latin, load two weights, and check what the browser actually downloaded rather than what the stylesheet asked for.
  • Images served at desktop dimensions to a 390 px screen. A 2400 px hero on a phone is roughly six times the pixels the screen can show.

Measure on the phone, throttled, on the deployed site. A local build on a laptop tells you nothing about any of the three.

The five-minute check

Do this on a real phone, not in a desktop browser's device mode:

  1. Load the product page. Is the price visible without scrolling? Is the button?
  2. Count how many things you had to dismiss before you could read anything.
  3. Tap the smallest control on the page. Did you hit it first time?
  4. Select a variant. Did anything jump sideways or vertically?
  5. Scroll to the bottom and back. Did the page ever move horizontally?
  6. Start the checkout and tap the last field. Is the pay button behind the keyboard?

Six questions, and each failure has a named fix above. This is worth running before any redesign, because a page that fails four of six will not be rescued by a new colour palette.

Components that are built at 390 first — and that carry the tabular figures, the safe-area padding and the observer rather than a scroll listener — are what uxgen hands a coding agent, as HTML you own. The mechanics behind why an agent produces the wide-first version by default are in why every Claude Code landing page looks the same.

FAQ

What screen width should I design an ecommerce page for first?

390 pixels, which is the width of the most common phone class in use. Design the first screen there and let it expand, because a narrow-first layout forces you to rank elements, and that ranking stays correct at every width. A wide-first layout stacks in source order when it compresses, which is how the price ends up below the fold.

Why is my add-to-cart button below the fold on mobile?

Almost always because of three elements added at different times: a sticky announcement bar, a hero image sized by aspect ratio rather than by height, and a cookie or newsletter overlay. Together they consume the roughly 640 logical pixels available on a 390-pixel screen before the product, the price and the button have been placed.

How do I stop the pay button hiding behind the keyboard on iOS?

Read window.visualViewport rather than window.innerHeight. The keyboard shrinks the visual viewport but not the layout viewport, so a fixed footer positioned against the layout viewport sits underneath it. Compute the hidden height, expose it as a CSS custom property, and add it to the footer's bottom padding alongside env(safe-area-inset-bottom).

What is the minimum tap target size on a product page?

44 pixels tall with about 8 pixels of clear space around it. Variant swatches are the usual failure, because a row of 32-pixel squares fits the design and not the thumb, and the resulting mis-taps are silent: the buyer sees a colour and a price they did not choose and leaves without telling you.

uxgen is a service of UXGen AI, LLC — 131 Continental Dr, Suite 305, Newark, DE 19713, United States.

© 2026 UXGen AI, LLC. All rights reserved.