Design Lounge
Install

Nº 149 of 520

Sticky mobile CTA bar

Sprout product page on a 390×844 phone: a sticky bottom bar keeps $48 and Add to bag, and hides the description once the page is scrolled 72px.

Preview

Open full size
390 × 844 · 8.2 KB · 123 lines

Use it

Free to use · credit appreciated

1 Once, in your project

npx skills add SusanAcharya/Design-Lounge

2 Then ask

Using the Design Lounge skill, build "Sticky mobile CTA bar" (piece cta-sticky-mobile-bar) in my stack. Match its brief; restyle it onto my product's theme.

No skill? Paste the brief instead. It carries the numbers, tokens and behaviour.

Brief

192 lines

Build brief for a coding agent. Rebuild this piece in the reader’s stack. If they haven’t said which stack, ask once, then default to semantic HTML + CSS + a little vanilla JS. Match the numbers below; don’t “improve” them.

What it is

A short product page for Sprout, a fictional houseplant starter kit, framed at 390×844 (mobile-web). The Lounge draws the phone chrome, so the page leaves 54px at the top and 34px at the bottom clear of fixed UI. A sticky bottom bar sits on that 34px inset: expanded it shows a one-line description, the price, and “Add to bag”; after the page scrolls 72px the description collapses to max-height: 0 and the bar shortens. Price and button never leave. One family, Sora. Coral #d4533e is the only saturated colour; leaf green is reserved for the kicker and icons.

Reference behaviour

  1. Initial state: cool off-white #f4f5f2. Top inset 54px, then a 52px sticky nav (“Sprout” + Kit). Hero: 11px uppercase kicker “Houseplant kit · ships Thursday”, 32px/600 title “Starter kit for a first windowsill”, 15px lede, a 168px CSS seedling illustration, $48 with “one kit · free post over $40”.
  2. Sticky CTA bar is position: fixed; left: 0; right: 0; bottom: 34px (or env(safe-area-inset-bottom, 34px)). Surface white, 1px top hairline, padding 12/20, grid 1fr auto with the description spanning both columns on row 1. Description: “Starter kit · 12 weeks of seed. Ships Thursday.” Price $48 + “incl. post”. Button 48px tall, pill, coral, 14px/600 “Add to bag”, min-width 128px.
  3. On scroll, if scrollY > 72, add .compact to the bar: padding 10/20, description max-height: 0; opacity: 0, hide the “incl. post” small. Price and button stay on one row. If scrollY ≤ 72, remove .compact. Nav gets .stuck (1px bottom border) when scrollY > 8.
  4. Body padding-bottom is 34px + 92px + 8px so the last review is not hidden behind the expanded bar.
  5. Add to bag writes “Starter kit added · $48” into a toast (role="status") above the bar, shown 2200ms. Toast does not persist.
  6. Reduced motion: scroll-behavior: auto; bar, description, toast and button transitions become 1ms. Compact still toggles, no lag.
  7. Hit targets: nav links 40px-class padding, button 48px, bar remains tappable in compact (button stays 48px).

Structure

390 × 844
┌─────────────────────────────┐
│     (54px lounge status)    │
│ Sprout                 Kit  │  sticky 52, top: 54
│ HOUSEPLANT KIT · SHIPS…     │
│ Starter kit for a first     │  h1 32/600
│ windowsill                  │
│ Soil, a 12-week seed tin…   │
│ ┌─────────────────────────┐ │  mock 168, CSS plant
│ └─────────────────────────┘ │
│ $48   one kit · free post   │
│ IN THE BOX                  │
│  • 2.4 L living soil        │
│  • Seed tin, 12 weeks       │
│  • Watering plan            │
│ FROM A WINDOWSILL IN PORTO  │
│  two review cards           │
│─────────────────────────────│
│ Starter kit · 12 weeks of seed. Ships Thursday. │  desc, hides when compact
│ $48              [Add to bag]│  always visible
│     (34px lounge home)      │  bar bottom: 34
└─────────────────────────────┘
  • <header class="nav" id="nav"> sticky, top: var(--safe-top).
  • .hero#top: kicker, h1, lede, .mock (decorative role="img"), .price-hero.
  • <section id="kit">: “In the box” + three <li> with 20px stroke icons.
  • Reviews section: two .rev cards.
  • <div class="bar" id="bar">: <p class="desc">, .amt, <button class="add" id="add">.
  • <div class="toast" id="toast" role="status" aria-live="polite">.

Viewport meta: width=device-width, initial-scale=1. First HTML comment: platform: mobile-web · 390x844.

Tokens

:root {
  --bg: #f4f5f2;               /* page */
  --surface: #fff;             /* bar, reviews */
  --sunk: #e8ebe4;
  --ink: #171c18;
  --ink-2: #5a635c;
  --ink-3: #8b928c;
  --line: #d8ddd4;
  --accent: #d4533e;           /* Add to bag */
  --accent-ink: #fff;
  --leaf: #3f6b48;             /* kicker, icons, logo dot */
  --sans: "Sora", system-ui, sans-serif;
  --safe-top: 54px;
  --safe-bot: 34px;
  --gutter: 20px;
  --r: 12px;
  --r-pill: 999px;
  --bar-h: 108px;              /* expanded, for body padding */
  --t-fast: 160ms;
  --t-bar: 280ms;
  --ease: cubic-bezier(.2, .7, .2, 1);
  --expo: cubic-bezier(.16, 1, .3, 1);
}

Typography

RoleFamilySizeWeightLine-heightTrackingCase
TitleSora32px6001.12−0.04emsentence
Price (hero)Sora28px6001−0.03em
Price (bar)Sora20px6001−0.03em
LogoSora16px6001−0.03emTitle
LedeSora15px4001.50sentence
Feature titleSora14px6001−0.02emsentence
ButtonSora14px60010Title
Bar descSora12px5001.40sentence
Kicker / h2Sora11/13px500/6001+0.12–.14emUPPERCASE

One family only. Do not add a serif.

Motion

ElementTriggerPropertyFrom → ToDurationEasing
Descriptioncompactmax-height, opacity36px + 1 → 0 + 0280 / 160ms--ease
Bar paddingcompactpadding12px → 10px280ms--ease
Toastaddopacity, translateY0 + 8px → 1 + 0160 / 280ms--expo
Buttonhover/activebackground, scaleaccent → #b84634 / 0.98160ms--ease
Nav borderscroll > 8border-colortransparent → line160ms—

Compact threshold is 72px, not intersection observer. Use { passive: true } on scroll.

States

  • Bar expanded: description visible, price shows “incl. post”.
  • Bar compact: description clipped, “incl. post” display: none. Button unchanged.
  • Nav stuck: 1px --line bottom border.
  • Add hover: #b84634. Active: scale 0.98.
  • Toast .on: visible 2200ms.
  • Focus-visible: 2px accent outline, 3px offset, on nav links and the button.

Accessibility

  • Bar is not role="toolbar"; it is a product CTA. The button is the control. Description is a <p> and remains in the DOM when compact (hidden visually, still in the accessibility tree — acceptable because the same facts sit in the hero). If the host AT doubles it, add aria-hidden="true" on .desc while compact.
  • Toast uses role="status" / aria-live="polite".
  • Mock plant is role="img" with aria-label="Illustrated seedling in a pot".
  • Contrast: coral #d4533e with white text is about 4.6:1 at 14px/600. Ink on --bg exceeds 12:1. --ink-2 on --bg is about 6:1.
  • Hit targets ≥ 40px. Button is 48px. Compact bar still 48px tall in the button column.
  • Body padding-top: 54px so content never sits under lounge chrome. Bar bottom: 34px so it never sits on the home indicator.

Responsive rules

This piece is specified at 390×844. If shown elsewhere:

  • 360 wide: gutters 16, title 28px, button min-width 112px. Compact threshold stays 72px.
  • Tablet width: keep the bar full-bleed at the bottom; do not convert it to an inline desktop CTA. Max readable measure 40ch on the hero.

Acceptance checklist

  • Frame is mobile-web 390×844; header comment says so; viewport includes initial-scale=1.
  • 54px top inset and 34px bottom inset are free of fixed chrome. Nav is sticky under the 54px. Bar is fixed at bottom: 34px.
  • Expanded bar shows description + $48 + “Add to bag”. After 72px of scroll the description is gone; price and button remain.
  • Compact transition is 280ms on cubic-bezier(.2,.7,.2,1) for max-height.
  • Add to bag shows a polite status toast for 2200ms with the $48 amount.
  • Only Sora loads. Accent is coral #d4533e, not leaf-green filled buttons (leaf is kicker/icons only).
  • Feature list has three items with 20px stroke SVG icons.
  • Focus rings are 2px #d4533e with a 3px offset.
  • Reduced motion: compact still happens, transitions 1ms, no smooth scroll.
  • Last review is not hidden behind the expanded bar (body padding-bottom includes --bar-h).

Implementation notes

Keep the bar off the home indicator with a token, not magic numbers in four places:

:root { --safe-top: 54px; --safe-bot: 34px; --bar-h: 92px; }
body {
  padding-top: var(--safe-top);
  padding-bottom: calc(var(--safe-bot) + var(--bar-h) + 8px);
}
.nav { position: sticky; top: var(--safe-top); }
.bar { position: fixed; left: 0; right: 0; bottom: var(--safe-bot); }

Collapse with max-height, not display: none, so it can animate:

.bar .desc {
  grid-column: 1 / -1;
  max-height: 20px;
  opacity: 1;
  overflow: hidden;
  transition: max-height 280ms var(--ease), opacity 160ms var(--ease);
}
.bar.compact .desc { max-height: 0; opacity: 0; }

Threshold on scrollY, passive listener:

addEventListener('scroll', () => {
  bar.classList.toggle('compact', scrollY > 72);
  nav.classList.toggle('stuck', scrollY > 8);
}, { passive: true });

Common mistakes: bottom: 0 so the bar sits on the lounge home indicator. Using position: sticky on the bar (it will not pin above the 34px inset). Two font families. A green primary button that collides with the existing Loam savings piece.

Details

Palette
Type
Sora
Motion
Subtle motion
Build
One session
Designed bySusan AcharyaKathmandu

Studied

Stripe

Let one big moving thing carry the colour. Keep the words light and the cards real.

All sources