Design Lounge
Install

Nº 027 of 520

Large title collapse on scroll

A 34px serif large title shrinks and fades into a 17px centred toolbar title as the list scrolls; the glass toolbar's fill, blur and hairline scale with scroll and the search field collapses.

Preview

Open full size
390 × 844 · 8.3 KB · 91 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 "Large title collapse on scroll" (piece ios-large-title-collapse) 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

198 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

The “Library” screen of “Tessel”, a reading app, showing the iOS large-title navigation pattern. At rest the toolbar is transparent and the 34px Playfair Display title sits in the content with a subtitle and a 36px search field under it. Over the first 52px of scroll, a single custom property --p (0 → 1) drives everything: the large title scales to 82% and fades out, the 17px centred Inter title rises 6px and fades in during the last 45% of the range, the toolbar gains a 78%-opaque paper fill, 20px blur and a hairline, and the search field’s height, margin and opacity collapse to 0. There is no JS animation; JS writes one number per frame and CSS does the rest, which is why it stays in sync with the finger.

Reference behaviour

  1. Initial state (scrollTop 0, --p: 0): toolbar shows ”‹ Shelves” (left) and “Edit” (right) in the terracotta accent on a transparent bar; large “Library” title with “Tessel · 24 titles, 6 in progress” under it; search field visible; book list below in three sections.
  2. Scroll down 0 → 52px: --p rises linearly (scrollTop / 52, clamped). The large title scales from 1 to 0.82 around its bottom-left corner, moves up 4px and fades (opacity 1 − 1.4p, so it is gone by p ≈ 0.71). The search field height goes 36 → 0px, top margin 12 → 0, opacity 1 − 1.6p, scale 1 → 0.94.
  3. From p = 0.55 to 1 the centred small title fades in (clamp(0, (p − .55)/.45, 1)) while rising from 6px to 0.
  4. The toolbar’s background alpha is .78p, its backdrop-filter blur is 20px × p, its bottom hairline alpha .08p and its inner top highlight .6p. At p = 1 it is fully glass; the list scrolls under it.
  5. Scroll back up: everything reverses continuously; there is no snapping and no hysteresis.
  6. Scrolling beyond 52px changes nothing further in the toolbar.
  7. Tapping “Shelves” or “Edit” has no navigation in the demo; they show focus rings and press states.
  8. Tapping the search field focuses the input (2px accent ring); it stays focusable only while --p < 1 in practice, because its height is 0 when collapsed.

Structure

390 × 844
┌────────────────────────────────────────┐
│ (54 status)                            │
│ 54 ‹ Shelves        [Library]     Edit │  .bar 44 tall (fixed), mid title hidden at p=0
│────────────────────────────────────────│  hairline alpha .08·p
│ 98  Library (Playfair 34/600)          │  .head, in the scroller
│     Tessel · 24 titles, 6 in progress  │
│    ┌──────────────────────────────┐    │  .search 36, r10
│    │ ⌕ Search titles, authors…    │    │
│    └──────────────────────────────┘    │
│  READING NOW                           │  12/600 uppercase
│  ▮ The Quiet Harbour            62 %   │  row 86: cover 44×62, title, author, progress bar 44×3
│    Ingrid Solheim · 412 pages   ───    │
│  ▮ Salt and Cinder              18 %   │
│  …                                     │
│  UP NEXT                               │
│  …                                     │
│  FINISHED THIS YEAR                    │
└────────────────────────────────────────┘
        after 52px of scroll:
┌────────────────────────────────────────┐
│ ‹ Shelves        Library          Edit │  glass toolbar, 17/600 centred title
│────────────────────────────────────────│
│  READING NOW …                         │
  • <header class="bar">: fixed, height: 54 + 44px, padding-top:54px, flex row with two <button>s and an absolutely positioned .mid title (aria-hidden, because the <h1> remains the accessible title).
  • <main>: the scroller, padding-top: 98px. .head holds <h1> (with a <small> subtitle) and .search (<label> wrapping an <input type="search">).
  • .sec uppercase labels; <a class="book"> rows: grid 44px 1fr auto — .cover (solid colour with a 4px spine highlight and an inset right shadow), title + author, .pct with a 44×3 progress bar.

Tokens

:root {
  /* colour — warm paper, ink-brown text, terracotta accent */
  --bg: #fbf8f3;
  --surface: #ffffff;
  --ink: #221f1b;
  --ink-2: #6d675e;         /* subtitle, author */
  --ink-3: #a39c91;         /* section labels, placeholder, percentages */
  --line: #e9e3d8;          /* row hairlines, progress track */
  --accent: #b5442f;        /* toolbar buttons, progress fill, focus */
  --accent-soft: #f6e6e1;
  --glass-rgb: 251, 248, 243;                 /* toolbar fill, alpha = .78·p */
  --glass-line: rgba(34,31,27,.08);           /* alpha scaled by p */
  --search-bg: rgba(34,31,27,.06);
  --covers: #3f5f7a #b5442f #5a7a4f #d9a441 #2f2d2a #8a6a9c #c97c5d #4f6f8a;

  /* type */
  --serif: "Playfair Display", Georgia, serif;
  --sans: "Inter", system-ui, -apple-system, sans-serif;

  /* geometry */
  --status: 54px;  --bar-h: 44px;  --content-top: 98px;
  --title-lg: 34px;  --title-sm: 17px;  --title-scale: .82;  --title-lift: 4px;
  --search-h: 36px;  --search-gap: 12px;
  --range: 52px;                              /* scroll distance for p: 0 → 1 */
  --mid-start: .55;                           /* p where the small title begins */
  --blur-max: 20px;  --fill-max: .78;
  --cover-w: 44px;  --cover-h: 62px;  --r-cover: 6px;  --r-search: 10px;
  --bar-w: 44px;  --bar-hh: 3px;              /* progress bar */

  /* motion — none by duration; everything is scroll-linked */
  --t-micro: 160ms;
  --ease: cubic-bezier(.2, .7, .2, 1);
}

Typography

RoleFamilySizeWeightLine-heightTrackingCase
Large titlePlayfair Display34px6001.15−0.01emsentence
SubtitleInter13px5001.40sentence
Toolbar titleInter17px6001−0.01emsentence
Toolbar buttonsInter15px50010sentence, --accent
SearchInter15px40010placeholder --ink-3
Section labelInter12px6001.3+0.08emUPPERCASE
Book titleInter15px5001.3−0.01emsentence
Author / pagesInter13px4001.450sentence
PercentInter12px5001.30tabular-nums

Motion

All values are functions of --p = clamp(scrollTop / 52, 0, 1), written once per animation frame. No durations except the micro states.

ElementPropertyAt p = 0At p = 1Formula
h1opacity10 (from p ≈ .71)1 − 1.4p
h1transformscale 1scale .82, up 4pxscale(1 − .18p) translateY(−4p px), origin left bottom
.searchheight36px036px × (1 − p)
.searchmargin-top12px012px × (1 − p)
.searchopacity / scale1 / 10 (from p ≈ .63) / .941 − 1.6p, 1 − .06p
.barbackgroundtransparentrgba(251,248,243,.78)alpha .78p
.barbackdrop-filterblur 0blur 20pxblur(20px × p) saturate(160%)
.barborder-bottomalpha 0alpha .08.08p
.bar .midopacity01 (from p = .55)clamp(0, (p − .55)/.45, 1)
.bar .midtransformdown 6px0translateY(6px × (1 − p))
toolbar button:active——opacity .6, 160ms --ease

Reduced motion: nothing to reduce; scroll-linked values are not animations. Optionally make the small title appear at p ≥ .55 without the 6px rise.

States

  • Rest: --p: 0; .bar transparent; .mid hidden.
  • Collapsed: --p: 1; also .bar.collapsed is set at p ≥ .55 for hooks (e.g., switching the status-bar style).
  • Toolbar button focus-visible: 2px --accent outline, 2px offset, 8px radius. Active: opacity .6.
  • Search focus-visible: 2px --accent box-shadow on the input.
  • Book row focus-visible: 2px --accent box-shadow, 8px radius.
  • Progress: .pct bar fill width = --w (62%, 18%, 87%); “New” rows 0%, “Done” rows 100%.

Accessibility

  • The <h1> in the content is the accessible page title at all times; the toolbar’s .mid copy is aria-hidden so the title is not announced twice.
  • Toolbar buttons are real <button>s with text labels (“Shelves” has a chevron icon plus text).
  • The search input has aria-label="Search library" and a visible placeholder. When collapsed it is still in the DOM; if your platform requires, set tabindex="-1" while --p ≥ 1.
  • Book rows are <a> elements with the title and author as the accessible name; progress is visible text (“62 %”) plus a decorative bar.
  • Contrast: --ink-2 on --bg 5.9:1; --accent on --bg 5.2:1; --ink-3 only at 12px labels beside stronger text.
  • The scroll listener is passive: true and throttled with requestAnimationFrame.

Responsive rules

  • 360 wide: large title 30px, everything else unchanged; the collapse range stays 52px.
  • ≥ 430 wide: content column max-width: 430px centred; the toolbar’s buttons and title share the same column.
  • Tablet: keep the large title but do not collapse the search field (there is room); toolbar still gains the glass fill.
  • If the list is shorter than the viewport + 52px, the collapse can never complete; pad the scroller’s bottom so scrollHeight − clientHeight ≥ 52 + 8.

Acceptance checklist

  • --p equals clamp(scrollTop / 52, 0, 1) and is updated inside requestAnimationFrame from a passive scroll listener.
  • Large title is Playfair Display 34/600 at rest and reaches scale(.82) with opacity 0 by the end of the range, scaling around its bottom-left.
  • The centred toolbar title is Inter 17/600, invisible until p = .55 and fully visible at p = 1, rising 6px as it appears.
  • Toolbar fill is rgba(251,248,243,.78) at p = 1 with blur(20px) saturate(160%), a 1px rgba(34,31,27,.08) hairline and an inset white highlight; all four scale with p.
  • The search field’s height, margin, opacity and scale collapse with p; at p = 1 it occupies 0px.
  • Scrolling back restores every value continuously; no thresholds except the small title’s start.
  • The toolbar is fixed and 98px tall including the 54px status area; content starts at 98px.
  • Book rows use a 44×62 cover with a 6px radius, 4px spine highlight and a 44×3 progress bar filled to --w.
  • All interactive elements (2 toolbar buttons, search input, 12 rows) show a visible focus ring.
  • No JS-driven animation loops; the only script is the scroll → --p bridge (about 6 lines).

Implementation notes

One number, many properties. Write --p on <body> and let each rule derive from it with calc(); alpha channels and blur radii accept calc:

body { --p: 0; }
.bar { background: rgba(var(--glass-rgb), calc(.78 * var(--p)));
  backdrop-filter: blur(calc(20px * var(--p))) saturate(160%);
  border-bottom: 1px solid rgba(34,31,27, calc(.08 * var(--p))); }
h1 { transform-origin: left bottom; opacity: calc(1 - var(--p) * 1.4);
  transform: scale(calc(1 - .18 * var(--p))) translateY(calc(-4px * var(--p))); }
.bar .mid { opacity: clamp(0, calc((var(--p) - .55) / .45), 1);
  transform: translateY(calc(6px * (1 - var(--p)))); }
.search { height: calc(36px * (1 - var(--p))); margin-top: calc(12px * (1 - var(--p)));
  opacity: calc(1 - var(--p) * 1.6); overflow: hidden; }
const main = document.getElementById('main'); let ticking = false;
function update() { ticking = false;
  const p = Math.min(1, Math.max(0, main.scrollTop / 52));
  document.body.style.setProperty('--p', p.toFixed(3)); }
main.addEventListener('scroll', () => { if (!ticking) { ticking = true; requestAnimationFrame(update); } }, { passive: true });
update();

Collapsing the search field shrinks the content, so the section below it moves up faster than the finger during the first 52px. That is the platform behaviour; do not “fix” it by animating a spacer. Do make sure there is enough content below so scrollTop can reach 52 without the shrinking height clamping it (otherwise --p oscillates).

Common mistakes: putting the large title inside the fixed bar and animating font-size (layout thrash, and it no longer scrolls with content); using transition on the scroll-linked properties (adds lag); forgetting -webkit-backdrop-filter; giving .mid a role or leaving it in the accessibility tree; listening for scroll on window when the scroller is <main>.

Details

Palette
Type
Playfair Display · Inter
Motion
Subtle motion
Build
One session
Designed bySusan AcharyaKathmandu

Studied

Susan Acharya

Draw one scene and let it move a little. The words stay calm on top.

All sources