A Purely CSS Shrinking Header

Sticky headers are a common pattern, but the classic “shrinking” effect—where a fat header condenses as you scroll—usually calls for JavaScript. Since position: sticky landed in CSS, there’s a way to pull it off with markup and styles alone.

It’s worth stepping back before diving in: sticky headers aren’t universally a good idea. They eat vertical space, and if they cover page content, that’s effectively data loss for the user. Whether the pattern fits your site depends on your content and whether persistent navigation genuinely adds value.

Here’s the technique, starting with the structure. The markup is intentionally plain—a <header> wrapping a single inner <div>, which holds the logo and navigation.

<header class="header-outer">
  <div class="header-inner">
    <div class="header-logo">...</div>
    <nav class="header-navigation">...</nav>
  </div>
</header>

Two Containers, Two Heights

The outer <header> gets a height of 120px, becomes a flex container that centers its child, and is made sticky.

.header-outer {
  display: flex;
  align-items: center;
  position: sticky;
  height: 120px;
}

The inner container (.header-inner) is, functionally, the real header—it contains the logo and nav. The outer element exists solely to provide extra height that the header can “shrink” from. Give the inner container a height of 70px and make it sticky as well.

.header-inner {
  height: 70px;
  position: sticky;
  top: 0; 
}

That top: 0 on the inner container is what pins it to the viewport’s top edge once it sticks.

The Negative Offset Trick

Here’s the core mechanic: for the inner container to stick to the top of the page, the outer <header> needs a negative top value equal to the difference between the two heights. That’s 70px minus 120px, which leaves -50px.

.header-outer {
  display: flex;
  align-items: center;
  position: sticky;
  top: -50px; /* Equal to the height difference between header-outer and header-inner */
  height: 120px;
}

With that in place, the outer header slides out of view as you scroll, while the inner container settles neatly at the top of the viewport.

Extending the Pattern

The same approach works beyond headers. Want a persistent alert banner that stays visible? Apply the same two-container structure and negative offset to achieve it without JavaScript.

That flexibility is impressive, but the technique has real constraints. Both the inner and outer containers rely on fixed heights, so the effect breaks if content changes—for instance, if navigation items wrap due to limited space. The bigger limitation is that the logo itself can’t shrink; since logos often occupy the most header space, that’s a meaningful drawback. A future CSS that allows styling based on an element’s sticky state could solve this, but that isn’t available today.