WebberUI

Large Title Collapse Header

An iOS-style large-title header that shrinks into a centered small title as a shared element on scroll, pulls out a search bar with a rubber-band bounce, and sticks section headings one after another below the header.

Loading preview…
npx shadcn@latest add https://webberui.com/r/large-title-collapse-header.json

Playground

Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.

44
52
<LargeTitleCollapseHeader />

Installation

npx shadcn@latest add https://webberui.com/r/large-title-collapse-header.json

Usage

The component is itself the scroll container — set a height in className and wrap each section in LargeTitleSection. As you scroll, the large title shrinks into the nav bar's centered small title and the section headings stick one after another below the nav bar:

import {
  LargeTitleCollapseHeader,
  LargeTitleSection,
} from "@/components/ui/large-title-collapse-header";

<LargeTitleCollapseHeader title="Settings" search className="h-[520px]">
  <LargeTitleSection title="Favorites">
    <ul>…</ul>
  </LargeTitleSection>
  <LargeTitleSection title="General">
    <ul>…</ul>
  </LargeTitleSection>
</LargeTitleCollapseHeader>

With search enabled, touch devices can pull down at the top of the page to pull out the search bar with a rubber-band bounce, while non-touch devices use the search button on the right of the nav bar. The search field supports controlled and uncontrolled modes:

const [query, setQuery] = React.useState("");

<LargeTitleCollapseHeader
  title="Settings"
  search
  searchPlaceholder="Search settings"
  searchValue={query}
  onSearchChange={setQuery}
>
  {/* sections filtered by query */}
</LargeTitleCollapseHeader>

Props

LargeTitleCollapseHeader

PropTypeDefaultDescription
titleReact.ReactNodeThe header's large title; on scroll it shrinks into the nav bar's centered small title as a shared element
childrenReact.ReactNodeThe content area, usually holding a number of LargeTitleSections
leadingReact.ReactNodeLeft slot of the nav bar (a back button, say)
trailingReact.ReactNodeRight slot of the nav bar (action buttons, say); with search enabled, the search button comes after it
searchbooleanfalseEnable the pull-down search bar and the nav bar search button
searchPlaceholderstring"搜尋"Placeholder for the search field
searchValuestringControlled search string; pair it with onSearchChange
defaultSearchValuestring""Default search string in uncontrolled mode
onSearchChange(value: string) => voidSearch-string change callback
navHeightnumber44Nav bar height (px), which is also where section headings stick
largeTitleHeightnumber52Large title row height (px), which determines the scroll distance of the shrink transition
classNamestringClass for the outer scroll container (set the height here)
contentClassNamestringClass for the wrapper around the content area

LargeTitleSection

PropTypeDefaultDescription
titleReact.ReactNodeSection heading, which sticks below the nav bar
childrenReact.ReactNodeSection content
idstringAnchor id applied to the section heading
classNamestringClass attached to the outermost section
headerClassNamestringClass attached to the sticky heading row (to override the background color to match the page, for example)

How it works

  • The shrink is a shared-element-style crossfade: the large title scales down, moves up, and fades out behind the nav bar as you scroll, while the nav bar's centered small title scales up and fades in at the same time — the two hand off into one continuous morph
  • Every transition is driven by scroll offset from useScroll (mapped directly by useTransform), so it never relies on scroll events to relayout and scroll performance matches native
  • The nav bar's background color and separator only fade in during the collapse, keeping the header clear in the expanded state
  • The search bar carries its expanded height in a useMotionValue: a touch pull-down computes the rubber-band offset with exponential damping, and on release a threshold decides between expanding and bouncing back; when the rubber band overstretches, the search field stretches with it
  • Section headings stick below the nav bar with native position: sticky; top: navHeight; as the next section reaches the sticky line it takes over from the previous one, one after another
  • The nav bar and section headings use backdrop-blur translucent backgrounds, keeping a sense of depth as content scrolls past

Accessibility

  • The large title is a real heading semantically (<h2>), while the small title in the nav bar is purely visual and marked aria-hidden so it is not announced twice
  • Section headings are <h3> and can take an id to serve as anchors
  • The search button is a native <button> marked with aria-expanded; while the search bar is collapsed the whole thing is aria-hidden and the input has tabIndex={-1} so it cannot be focused from the keyboard, becoming focusable once expanded, with Escape collapsing it
  • When the user has "reduce motion" enabled at the system level, the search bar's expand / bounce becomes an instant change (the spring bounce is disabled), while the pull-down gesture and the scroll collapse still work
  • Timers, event listeners, and gesture bindings are all cleaned up on unmount

On this page