WebberUI

Hero Condense Header

As the page scrolls, the hero's title, avatar, and meta migrate one by one into a sticky header via shared-element FLIP, then expand back into place in order when you scroll up.

This is a WebberUI Pro component

Free during the launch campaign: sign up or sign in, then hit “Copy install command” in the preview above and it installs straight away — no payment, no credit card. The command below returns 401 while you are signed out.

How to install Pro components →See the plans →

Loading preview…
npx shadcn@latest add "https://webberui.com/r/hero-condense-header.json?t=<install token>"

Playground

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

240
56
0.5
<HeroCondenseHeader />

Installation

npx shadcn@latest add "https://webberui.com/r/hero-condense-header.json?t=<install token>"

Usage

Every entry in the items array is the same shared node migrating between the hero and the sticky bar. heroClassName positions its hero anchor (which determines its position and size when expanded), and headerClassName determines how it lays out in the sticky bar once condensed:

import {
  HeroCondenseHeader,
  type HeroCondenseItem,
} from "@/components/ui/hero-condense-header";

const items: HeroCondenseItem[] = [
  {
    id: "avatar",
    content: <img src="/me.jpg" alt="Aria" className="h-full w-full rounded-full" />,
    heroClassName: "inset-x-0 mx-auto top-9 h-16 w-16",
    headerClassName: "order-1 h-8 w-8 shrink-0",
  },
  {
    id: "title",
    content: <span className="whitespace-nowrap text-sm font-semibold">Aria Nakamura</span>,
    heroClassName: "inset-x-0 mx-auto top-[104px] h-7 w-56",
    headerClassName: "order-2",
  },
  {
    id: "meta",
    content: <span className="whitespace-nowrap text-xs text-neutral-500">Product Designer</span>,
    heroClassName: "inset-x-0 mx-auto top-[136px] h-5 w-64",
    headerClassName: "order-3 ml-auto",
  },
];

<HeroCondenseHeader items={items} heroHeight={240} headerHeight={56}>
  <YourPageContent />
</HeroCondenseHeader>

The scroll container can be the page itself (the window is the default scroll container) or any overflow-y: auto block — for the latter, pass that container's ref to container (as shown above).

Array order is condense order: the first item condenses first and expands last; the visual arrangement within the bar is controlled independently with order-* utilities.

Props

PropTypeDefaultDescription
itemsHeroCondenseItem[]Shared elements that migrate in sequence; array order is condense order
heroHeightnumber240Total hero height (px)
headerHeightnumber56Sticky header bar height (px)
condenseSpannumber0.5Share of the progress a single item's condense takes (0.05–1); smaller values make the staggering more obvious
containerReact.RefObject<HTMLElement | null>Scroll container ref; required for a nested overflow container, defaults to the window
heroClassNamestringHero layer styling (background, corner radius, and so on)
barClassNamestringSticky bar container styling
childrenReact.ReactNodeThe page content below the header
classNamestringAppended to the outermost container's className

HeroCondenseItem

FieldTypeDescription
idstringUnique identifier, used to pair the hero anchor with the sticky slot
contentReact.ReactNodeShared element content (the same node migrates between the two ends)
heroClassNamestringHero anchor positioning (absolute); its height sets the expansion ratio and its center sets the landing point
headerClassNamestringSticky slot layout; use order-* to adjust the order within the bar

How it works

  • The expansion ratio is anchor height / slot height. The shared node takes its condensed (sticky bar) size as the baseline and scales up to hero size via transform — which means text color does not change during the migration, so pick a hero background that works with the sticky bar
  • Geometry at both ends is measured along the offsetParent chain (layout coordinates before transforms), so it is unaffected by scrolling and sticky offsets. Position your anchors with layout properties such as left/top/inset/mx-auto and avoid transform utilities (for example -translate-x-1/2), which throw the measurement off
  • The whole migration is driven by transform alone and never triggers a layout reflow; a ResizeObserver re-measures whenever sizes or the window change
  • Each item tweens along its own progress window (condenseSpan wide, staggered against the others), so condensing and expanding happen one by one; the smaller condenseSpan is, the more obvious the staggering

Accessibility

  • The shared text (name, job title) is rendered once and is always in the DOM, so assistive technology reads it normally; the hero layer's positioning anchors are empty and marked aria-hidden
  • When the user has "reduce motion" enabled at the system level, a static sticky header (the condensed state) is rendered directly — no migration animation, no hero region — and it is still a usable header
  • The sticky bar uses a semantic <header> element, forming the page's banner landmark

On this page