Skip to content
WingoUI
ComponentsTemplatesDocsPricingAI agentsBlogChangelogContact
Components
Buttons & Actions
ButtonButton GroupCopy ButtonDelete ButtonTheme ToggleToggleToggle Group
Inputs
Avatar UploadCalendarCheckboxColor PickerComboboxCountry SelectCron PickerCurrency InputDate PickerDate Range PickerDate Time PickerDuration PickerEmail InputEmoji PickerFile UploadFilter BuilderInputInput GroupListboxMasked InputMention InputMonth PickerMulti SelectNumber InputOTP InputPassword InputPhone InputRadio GroupRatingSearch InputSegmented ControlSelectSignature PadSliderSwitchTag InputTextareaTime PickerTimezone SelectTree Select
Forms
Address InputBooking WidgetCSV ImportFieldForm WizardInline EditInvoice line itemsRich Text EditorSchema Builder
Navigation
Back To TopBounce SidebarBreadcrumbCommand MenuGlobal SearchGooey NavHook SidebarNav MenuNav TabsPaginationProximity SidebarScroll ProgressSidebarStepsTable Of ContentsTabsTopbarUser MenuWorkspace Switcher
Overlays
Alert DialogContext MenuDialogDrawerDropdown MenuFamily DrawerHover CardPopoverShare DialogSheetShortcuts DialogTooltipTour
Feedback
AlertBannerCookie ConsentEmoji ReactionEmpty StateNotification BellNotification CenterProgressRoute ProgressSetup ChecklistSkeletonSpinnerToast
Data Display
Approval FlowAvatarBadgeCalendar ViewCode BlockComment ThreadContact cardCountdownDescription ListDiff ViewerFile ListFile managerFolderGanttJSON ViewerKbdLog ViewerMarkdownPresenceSchedulerTagTimelineTree View
Tables & Lists
Audit LogData TableDeal PipelineEditable GridKanbanListMembers tablePermissions MatrixTableTask ListVirtual List
Charts & Stats
Animated CounterArea chartBar chartDonut chartFunnel chartGaugeGitHub ActivityHeatmap calendarLeaderboardLine chartRadar chartRadial barSparklineStat CardTrackerTrend badge
Layout
AccordionApp ShellCardCollapsibleDetail LayoutEntity HeaderPage HeaderScroll AreaSeparatorSettings LayoutSplit ViewToolbar
Media
Audio PlayerCarouselCSV ViewerDOCX EditorDOCX ViewerExcel ViewerFile SystemImage compareImage CropperImage GalleryImage LightboxLocation MapPDF ViewerPowerPoint ViewerQR CodeStep PlayerVideo Player
AI Kit
Agent Status CardChat MessageChat ThreadChat WidgetConversation ListFluid OrbGrid RevealMatrix OrbModel PickerPrompt InputReasoning IndicatorRule BuilderSuggestion ChipsTool Call TimelineUsage MeterVoice InputWorkflow Canvas
Text & Effects
Background PatternGravity LettersMarqueeTerminalText AnimateText Rotate
Mobile
Action SheetBottom Tab BarFABHaptic ButtonHero HeaderInstall PromptLarge Title HeaderNotification BannerOnboarding CarouselPicker WheelPull To RefreshSlide To ConfirmSticky CTA BarSwipe DeckSwipeable Tabs
Commerce
Cart drawerCategory pickerCheckout stepsCoupon inputFavorite buttonFilter panelListing cardListing gridOrder trackerPricePrice rangePricing tableProduct galleryQuantity stepperReviewsSeller card
Marketing Sections
App Download SectionBlog Cards SectionCTA SectionFAQ SectionFeature GridFeature ShowcaseHero SectionIntegrations SectionLogo CloudMarketing FooterMarketing HeaderMarketing SectionNewsletter SectionPricing SectionStats SectionSteps SectionTeam SectionTestimonials SectionTimeline SectionVideo Section
Blocks
Admin Dashboard PageAdmin ShellAI Chat PageAPI Keys PageAutomation Builder PageCheckout PageCRM Contact PageCRM Pipeline PageError PagesInbox PageInvoice PageKPI DashboardListing Detail PageLogin PageMarketplace Home PageMarketplace Search PageMarketplace ShellMobile App HomeOnboarding FlowOrder History PagePost Listing WizardPricing PageProfile PageRegister PageSettings PageUsers Admin Page
Templates
About PageAcademy PageAccessibility PageAffiliate PageAgency PageAI Product PageAppointment Booking PageBlog PageBlog Post PageBug Bounty PageCareers PageChangelog PageCommunity PageCompare PageContact PageCustomer Story PageCustomers PageDemo Booking PageDevelopers PageDocs Home PageDownload PageEnterprise PageEvents PageExperts PageFAQ PageFeature PageGlossary PageHelp Article PageHelp Center PageHome Services PageImpact PageIntegration Detail PageIntegrations PageJob Detail PageLanding PageLaunch PageLegal PageManifesto PagePortfolio PagePress PageProduct Tour PagePromo PageReal Estate PageReferral PageResearch Report PageRestaurant PageRoadmap PageROI Calculator PageSecurity PageSolutions PageStartups PageStatus PageStorefront PageSwitch PageTemplate Gallery PageTravel PageWaitlist PageYear in Review Page
Hooks & Utilities
AI ChatChart kitCommerceDate utilsFile exportFile typesFiltersForm validationHapticsInvoicingMotion presetsNavigationNumber formatRelative timeRomanian locationsSpreadsheetText matchTonesUIProvideruseClipboarduseConsentuseControllableStateuseCooldownuseDebounceuseKeyboardInsetuseLocaleuseLongPressuseMediaControlsuseMediaQueryuseOverflowItemsusePersistedStateuseScrollEdgesuseScrollStateuseSelectionuseShortcutuseStickToBottom
GalleryTheme

Mobile

Hero Header

Mobile

Hero Header React component for Tailwind CSS v4

Haptic ButtonInstall Prompt
Live props
Content
image
imageAlt
title
backLabel
backAriaLabel
Behavior
backHref
parallax0.5
stretch
Layout
height320px
safeArea
Appearance
buttonStyle
background
border
tone
color
scrim
fade
Code
import { HeroHeader } from "@/components/ui/hero-header"

<HeroHeader />
  1. Home
  2. Components
  3. Mobile
  4. Hero Header
Pro

Hero Header React component for Tailwind CSS v4

v1.0.0·Added in 1.0.0

A React photo hero header for phone detail pages: a full-bleed image under the status bar, glass buttons, parallax and a bar handover.

The photo fills the top of the page right up to the edge, under the status bar, with round frosted buttons on top (back, share, favorite) that stay readable on any photo. Scroll and the photo moves slower than the page and slowly darkens; in the last 32px before its bottom edge reaches the bar, the glass or solid bar fades in, the compact title rises into it and the round buttons lose their glass and turn into tinted bar buttons, all following your finger. Pull past the top on an iPhone and the photo stretches from its top edge instead of leaving a gap. With onMediaClick a tap on the photo opens it (a lightbox); on custom media like a carousel a tap on the slide opens it while swipes and its own buttons keep working. With a mouse everything is a click; with the keyboard Tab goes back, actions, then the photo button, each with a visible ring (inside the photo, with a white edge so it reads on dark and light photos). On touch the round buttons are 44px. Under reduced motion there is no parallax and no stretch, and the bar takes over in one short fade.

Hero Header is a Pro React component in the React mobile components category, written in TypeScript for React 19 and Tailwind CSS v4. It installs as source with npx wingo-ui@latest add hero-header, together with UIProvider, Button, Navigation, useScrollState, Motion presets and 1 more and the npm packages motion and lucide-react.

Add to a project

Requirements: React 19 and Tailwind CSS v4, in Next.js (App Router or Pages), Vite or React Router. Installation guide · Next.js setup · Vite setup

npx wingo-ui@latest add hero-header

Hero Header is part of Pro, with 298 more Pro items: $8 a month or $150 once. 14-day refund before you download any Pro item. See plans

Usage

Import HeroHeader from @/components/ui/hero-header.

"use client"

import { useState } from "react"
import { Heart, Share } from "lucide-react"
import { Button } from "@/components/ui/button"
import { HeroHeader } from "@/components/ui/hero-header"

export function ListingPage() {
  const [saved, setSaved] = useState(false)

  return (
    <>
      <HeroHeader
        image="/demo/apartment.jpg"
        title="2-bedroom apartment"
        backLabel="Listings"
        backHref="/listings"
        onMediaClick={() => console.log("open gallery")}
        labels={{ openMedia: "Open photos", imageError: "Photo unavailable" }}
        actions={
          <>
            <Button variant="ghost" size="icon" radius="full" aria-label="Share">
              <Share />
            </Button>
            <Button
              variant="ghost"
              size="icon"
              radius="full"
              aria-label="Save to favorites"
              aria-pressed={saved}
              onClick={() => setSaved((v) => !v)}
            >
              <Heart className={saved ? "fill-current" : undefined} />
            </Button>
          </>
        }
      >
        <span className="rounded-full bg-black/50 px-2.5 py-1 text-xs font-medium text-white backdrop-blur-md">1 / 12</span>
      </HeroHeader>
      <main className="px-4 py-5">
        <h1 className="text-2xl font-bold">2-bedroom apartment, split floor plan</h1>
        <p className="text-sm text-muted-foreground">Brooklyn, New York · $489,000</p>
      </main>
    </>
  )
}

Props

  • imagedefault ""
    string

    The photo's src. Loaded with fetchPriority="high" and object-cover; a softly pulsing bg-foreground/10 block of the same height holds its place while it loads, so nothing shifts. A broken src shows an icon and labels.imageError.

  • imageAltdefault ""
    string

    The photo's alt text. Leave empty when the photo is decorative (the page's <h1> says what it is).

  • media
    ReactNode

    Replaces the image: a Carousel, a video, a map. It gets the same parallax, stretch, scrim and shade. Size it to the hero with h-(--hero-header-height).

  • titledefault ""
    ReactNode

    The compact title that rises into the bar after the handover. It is aria-hidden: the page keeps its own <h1> below the hero. Without a title the bar shows only the buttons.

  • backLabeldefault ""
    string

    The back button's text, usually the previous page ("Listings"). The button renders when backHref or onBack is set; without a label it is a round chevron named by backAriaLabel.

  • backAriaLabeldefault "Back"
    string

    The back button's accessible name when it shows only the chevron.

  • backHrefdefault ""
    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 round buttons: plain <Button variant="ghost" size="icon" radius="full" aria-label="…" />s. The header restyles its direct button and link children per buttonStyle while they sit on the photo, and turns them into tinted bar buttons after the handover.

  • heightdefault 320
    number

    The hero's height in px, capped at 70dvh so a tall value never hides the whole page on a short phone.

  • buttonStyledefault "glass"
    "glass" | "solid" | "plain"

    The round buttons over the photo: frosted dark glass with white icons, a solid popover disc with a shadow, or bare white icons (use with the scrim). All become plain bar buttons after the handover.

  • backgrounddefault "blur"
    "blur" | "solid"

    The bar after the handover: frosted glass or the solid page color. Set [--bar-bg:var(--surface)] on the header when the page is not bg-background.

  • borderdefault "scrolled"
    "scrolled" | "always" | "never"

    The hairline under the bar: shown once the bar has taken over, always, or never.

  • tonedefault "neutral"
    "neutral" | "brand" | "success" | "warning" | "danger" | "info"

    The tint of the back button and the actions once they sit in the bar. Over the photo they stay white (glass, plain) or neutral (solid).

  • colordefault ""
    string

    Any CSS color for the bar buttons after the handover. Replaces the tone.

  • scrimdefault true
    boolean

    A top gradient (black 45% to clear) that stays under the bar while the photo scrolls, so the status bar and the buttons read on bright photos. Turn it off for dark photos.

  • fadedefault true
    boolean

    The photo darkens (up to 45%) as it scrolls away, which makes the handover to the bar feel deliberate.

  • parallaxdefault 0.5
    number

    How much slower the photo moves than the page: 0 scrolls with it, 0.5 at half speed, 1 stays put while the page slides over it. Off under reduced motion.

  • stretchdefault true
    boolean

    Pulling past the top (iOS overscroll, where the scroller reports negative offsets) scales the photo from its top edge instead of showing a gap. Nothing happens where browsers do not bounce. Off under reduced motion.

  • safeAreadefault true
    boolean

    The bar pads env(safe-area-inset-top) and the photo runs under the status bar in a standalone PWA. It is 0 in desktop browsers. A frame that fakes a status bar can set --hero-header-inset on the header instead.

  • onMediaClick
    () => void

    Makes the photo a button named labels.openMedia (open a lightbox). With custom media, a click on the media surface opens it (its own controls and drags do not), and a focusable button appears for the keyboard.

  • onCollapsedChange
    (collapsed: boolean) => void

    Called when the bar has fully taken over and when the photo comes back: switch the status bar theme color, log a view.

  • children
    ReactNode

    Pinned to the bottom of the photo: a "1 / 12" chip, badges. It fades out just before it slides under the bar. Chip look: rounded-full bg-black/50 px-2.5 py-1 text-xs font-medium text-white backdrop-blur-md.

  • scrollTarget
    HTMLElement | Window | RefObject<HTMLElement | null> | null

    What scrolls. Default: the nearest ScrollContainerProvider, else the window. Put the hero at the top of it.

  • linkComponent
    (props: LinkComponentProps) => ReactNode

    The router link for backHref on this header only. Usually set once for the app with LinkProvider.

  • labels
    Partial<HeroHeaderLabels>

    The strings: openMedia ("Open photos"), imageError ("Photo unavailable"). Defaults in DEFAULT_HERO_HEADER_LABELS.

  • classNames
    Partial<Record<"bar" | "surface" | "hairline" | "leading" | "back" | "title" | "actions" | "media" | "image" | "scrim" | "shade" | "overlay", 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 (not only while the hero is in view); it carries data-collapsed for group-data-[collapsed]/hero: styling and passes CSS variables down (--bar-bg, --hero-header-inset).

Parts

  • DEFAULT_HERO_HEADER_LABELS

    constant

    The English strings: openMedia ("Open photos") and imageError ("Photo unavailable"). Spread it and override what you translate.

Examples

Restaurant, solid

Bistro 21 with buttonStyle="solid" and background="solid".

Parallax 0 and 0.5

The same hero scrolling with the page and at half speed.

Carousel as media

Five photos in a Carousel; swipes page, a tap opens the gallery.

Scrim on and off

Plain white icons on a dark photo without the scrim and on a bright one with it.

CRM client, brand tone

A client profile in Northwind CRM whose bar buttons turn brand orange.

Broken photo

The error state: same height, an icon and a label.

Dependencies

motionlucide-reactui-configbuttonnavigationuse-scroll-statemotiontones

Files

  • components/ui/hero-header.tsx

FAQ

Is the React Hero Header component free?

The preview, props and docs are free; copying the source needs a Pro plan. See plans.

How do I install Hero Header?

Run npx wingo-ui@latest add hero-header. It also adds the Wingo UI items it builds on (UIProvider, Button, Navigation, useScrollState, Motion presets and 1 more) and the npm packages motion and lucide-react. With shadcn, add the @wingo-ui registry with your API key, then run npx shadcn@latest add @wingo-ui/hero-header.

See also

Large Title HeaderCarouselImage LightboxSticky CTA BaruseScrollStatehook

More React mobile components

Bottom Tab BarSlide To ConfirmAction SheetFABHaptic ButtonInstall Prompt
All 15 React mobile components

Articles using this component

  • React Collapsing Header: iOS Large Title and Photo Hero

    Mobile UI in React · Oct 16, 2026 · 8 min read

PreviousHaptic ButtonNext Install Prompt
2-bedroom apartment
Featured1 / 12
OwnerVerifiedNegotiable

2-bedroom apartment, split floor plan

$489,000

Brooklyn, Park Slope · Sep 21, 2026 · $843/sq ft

Area
580 sq ft
Rooms
2 bedrooms
Floor
3 of 4
Year built
2012
Parking
Private spot
Area
Park Slope

Description

A bright apartment with south-facing windows and a view of Prospect Park. Separate kitchen, furnished in 2024, a bathroom with a window and a 65 sq ft enclosed balcony.

A 5 minute walk from the subway and the shops on 7th Avenue. Sold furnished, with a parking spot in the courtyard. Viewings on weekends.

Amenities

  • Central heating
  • Enclosed balcony
  • Air conditioning
  • Furnished
  • Elevator
  • Basement storage

Seller

Listing ID 88412 · 1,284 views

$489,000

Negotiable

16:40
glass · blur · border scrolled · 320pxparallax 0.5 · stretch on · fade onscrim on · safe area onexpanded · photos opened 0 · back 0