# Large Title Header

> A React iOS large title header that shrinks into a compact title in a glass bar as you scroll.

- Type: React component (Mobile)
- Access: Pro (the source needs a Wingo UI Pro plan, see https://wingo-ui.com/pricing)
- Version: 1.1.0
- Install: `npx wingo-ui@latest add large-title-header`
- Docs: https://wingo-ui.com/components/large-title-header

## Interaction

Scroll the page and the large title slides up with the content, fading as it passes under the bar; exactly when it disappears the compact title rises into the bar and the glass surface and hairline fade in, all following your finger. Let go halfway through the handover and it settles to fully expanded or fully collapsed. Pull past the top on a phone and the title stretches up to 8% from its corner. Tap the search field and the page scrolls so the field sits right under the bar, the Cancel button slides in from the end and the field makes room; type to filter, tap the clear button to empty it, tap Cancel (or press Escape) to clear, close the keyboard and return to the top. The back button (a link or a button, labelled with the previous page) and the trailing actions stay in the bar at 44px on touch. With the keyboard Tab goes back, actions, search, cancel; the title is the page's only <h1>, and when Escape or Cancel ends a search from the keyboard, focus returns to it instead of getting lost. Under reduced motion the handover is an instant switch and nothing stretches or slides.

## Installation

```bash
npx wingo-ui@latest add large-title-header
```

Pro item: run `npx wingo-ui@latest login` once with a Pro account. The CLI adds the files, their dependencies and a version record for updates.

With shadcn: `npx shadcn@latest add @wingo-ui/large-title-header` (needs the @wingo-ui registry with your API key in components.json)

Files:

- `components/ui/large-title-header.tsx`

npm dependencies: `npm i motion lucide-react`

Wingo UI dependencies (installed automatically):

- [UIProvider](https://wingo-ui.com/components/ui-config) ([Markdown](https://wingo-ui.com/components/ui-config.md))
- [Button](https://wingo-ui.com/components/button) ([Markdown](https://wingo-ui.com/components/button.md))
- [Input](https://wingo-ui.com/components/input) ([Markdown](https://wingo-ui.com/components/input.md))
- [Navigation](https://wingo-ui.com/components/navigation) ([Markdown](https://wingo-ui.com/components/navigation.md))
- [useScrollState](https://wingo-ui.com/components/use-scroll-state) ([Markdown](https://wingo-ui.com/components/use-scroll-state.md))
- [useControllableState](https://wingo-ui.com/components/use-controllable-state) ([Markdown](https://wingo-ui.com/components/use-controllable-state.md))
- [Motion presets](https://wingo-ui.com/components/motion) ([Markdown](https://wingo-ui.com/components/motion.md))
- [Tones](https://wingo-ui.com/components/tones) ([Markdown](https://wingo-ui.com/components/tones.md))

## Usage

```tsx
"use client"

import { useState } from "react"
import { SquarePen } from "lucide-react"
import { Button } from "@/components/ui/button"
import { LargeTitleHeader } from "@/components/ui/large-title-header"

const conversations = [
  { id: "1", name: "Daniel Reed", message: "I sent proposal #Q-2026-118." },
  { id: "2", name: "Sophia Lee", message: "Invoice #2026-0412 was paid." },
]

export function MessagesPage() {
  const [query, setQuery] = useState("")
  const results = conversations.filter((c) => c.name.toLowerCase().includes(query.toLowerCase()))

  return (
    <>
      <LargeTitleHeader
        title="Messages"
        subtitle="3 unread"
        backLabel="Settings"
        backHref="/settings"
        search
        searchValue={query}
        onSearchChange={setQuery}
        actions={
          <Button variant="ghost" size="icon" radius="full" aria-label="New message">
            <SquarePen />
          </Button>
        }
      />
      <ul className="min-h-dvh">
        {results.map((c) => (
          <li key={c.id} className="px-4 py-3">
            <p className="font-medium">{c.name}</p>
            <p className="text-sm text-muted-foreground">{c.message}</p>
          </li>
        ))}
      </ul>
    </>
  )
}
```

## Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `title?` | `ReactNode` | `""` | The page title: large under the bar (the page's <h1>, see headingAs), compact in the bar once it scrolls away. Wraps to two lines when long; the compact copy truncates. |
| `headingAs?` | `"h1" \| "h2" \| "h3" \| "div"` | `"h1"` | The title's element, for the large title and, in compact mode, the bar title. Keep h1 for the screen's own header; use h2, h3 or div inside a page that already has its h1. |
| `subtitle?` | `ReactNode` | `""` | A muted line under the large title ("23 necitite"). |
| `compactTitle?` | `ReactNode` |  | A shorter title for the bar. Default: the title. |
| `backLabel?` | `string` | `""` | The back button's text, usually the previous page ("Settings"). The button renders when backHref or onBack is set; without a label it is a chevron named by backAriaLabel. |
| `backAriaLabel?` | `string` | `"Back"` | The back button's accessible name when it shows only the chevron. |
| `backHref?` | `string` | `""` | Makes the back button a link through NavLink (the app's router link). |
| `onBack?` | `() => void` |  | Runs when the back button is pressed (with backHref, before the navigation). |
| `actions?` | `ReactNode` |  | Trailing buttons in the bar: <Button variant="ghost" size="icon" radius="full" aria-label="…" /> for icons, variant="ghost" size="sm" for text. |
| `mode?` | `"collapse" \| "large" \| "compact"` | `"collapse"` | collapse: the large title scrolls away and hands over to the bar. large: the title stays pinned at full size and never collapses. compact: only the bar, with the title always in it (detail pages). |
| `titleSize?` | `"md" \| "lg"` | `"lg"` | The large title at 28px (md) or 34px (lg, iOS). |
| `tone?` | `"neutral" \| "brand" \| "success" \| "warning" \| "danger" \| "info"` | `"neutral"` | The tint of the back and cancel buttons and the search field's focus ring and caret (iOS tints them). Neutral keeps the default ring. |
| `color?` | `string` | `""` | Any CSS color for the back and cancel buttons and the search focus ring and caret. Replaces the tone. |
| `background?` | `"blur" \| "solid" \| "transparent"` | `"blur"` | The bar's surface once content is under it: frosted glass, the solid page color, or none. The bar is transparent over the large title in every mode. Set [--bar-bg:var(--surface)] when the page is not bg-background. |
| `border?` | `"scrolled" \| "always" \| "never"` | `"scrolled"` | The hairline under the bar: fades in with the surface, always visible, or never. |
| `search?` | `boolean` | `false` | A search field under the title. Focusing it scrolls it right under the bar and shows Cancel; Escape or Cancel clears, blurs and scrolls back to the top. Keep the results at least a screen tall while searching (min-h-full) so the field can stay under the bar. |
| `searchValue?` | `string` |  | The query, controlled. Cancel calls onSearchChange(""). |
| `defaultSearchValue?` | `string` | `""` | The first query while uncontrolled. |
| `onSearchChange?` | `(value: string) => void` |  | Every change of the query, typing, clearing and Cancel. |
| `onSearchingChange?` | `(searching: boolean) => void` |  | True while the search is active (focused or holding a query), so the page can swap to results. |
| `searchPlaceholder?` | `string` | `"Search"` | The field's placeholder and accessible name. |
| `cancelLabel?` | `string` | `"Cancel"` | The text of the button that ends the search. |
| `searchInputProps?` | `Partial<InputProps>` |  | Anything else for the search Input: name, maxLength, loading, a ref, extra handlers. |
| `stretch?` | `boolean` | `true` | The large title grows up to 8% from its top corner while you pull past the top (iOS overscroll, Safari only). Off under reduced motion. |
| `snap?` | `boolean` | `true` | A scroll that comes to rest halfway through the handover settles to fully expanded or fully collapsed, so the title never stays half under the bar (iOS). A page too short to collapse settles back to expanded. |
| `safeArea?` | `boolean` | `true` | Pads env(safe-area-inset-top) above the bar so it clears the notch in a standalone PWA. It is 0 in desktop browsers. |
| `scrollTarget?` | `HTMLElement \| Window \| RefObject<HTMLElement \| null> \| null` |  | What scrolls. Default: the nearest ScrollContainerProvider, else the window. |
| `linkComponent?` | `(props: LinkComponentProps) => ReactNode` |  | The router link for backHref on this header only. Usually set once for the app with LinkProvider. |
| `classNames?` | `Partial<Record<"bar" \| "surface" \| "hairline" \| "row" \| "leading" \| "back" \| "compactTitle" \| "actions" \| "large" \| "title" \| "subtitle" \| "search" \| "input" \| "cancel", string>>` |  | Extra classes for the inner parts. |
| `className?` | `string` |  | Extra classes for the root. The root is display: contents (so the bar sticks to the whole page), except in mode="large" where it is the sticky block. The root carries data-collapsed and data-searching for group- styling. |

## Examples

- **Back navigation**: A push from Settings to Notifications with a back button labelled with the previous page.
- **Large and compact**: mode="large" never collapses; mode="compact" is only the bar.
- **With a tab bar**: A searchable inbox between a large title and a bottom tab bar.
- **Medium, solid**: titleSize="md" on a surface, with a solid bar and a permanent hairline.
- **Over a hero photo**: White title over a photo; the bar turns to glass when it collapses.

## See also

- [Bottom Tab Bar](https://wingo-ui.com/components/bottom-tab-bar) ([Markdown](https://wingo-ui.com/components/bottom-tab-bar.md))
- [Topbar](https://wingo-ui.com/components/topbar) ([Markdown](https://wingo-ui.com/components/topbar.md))
- [useScrollState](https://wingo-ui.com/components/use-scroll-state) ([Markdown](https://wingo-ui.com/components/use-scroll-state.md))
- [Navigation](https://wingo-ui.com/components/navigation) ([Markdown](https://wingo-ui.com/components/navigation.md))
- [Input](https://wingo-ui.com/components/input) ([Markdown](https://wingo-ui.com/components/input.md))

---

Canonical: https://wingo-ui.com/components/large-title-header
More components: https://wingo-ui.com/components · Index for agents: https://wingo-ui.com/llms.txt
