I used to be just lately requested by a scholar to assist with a seemingly easy drawback. She’d been engaged on an internet site for a espresso store that sports activities a sticky header, and he or she needed the hero part proper beneath that header to span the remainder of the obtainable vertical area within the viewport.
Right here’s a visible demo of the specified impact for readability.
Appears prefer it ought to be simple sufficient, proper? I used to be certain (learn: overconfident) that the issue would solely take a few minutes to unravel, solely to seek out it was a a lot deeper properly than I’d assumed.
Earlier than we dive in, let’s take a fast take a look at the preliminary markup and CSS to see what we’re working with:
<physique>
<header class=”header”>Header Content material</header>
<part class=”hero”>Hero Content material</part>
<principal class=”principal”>Fundamental Content material</principal>
</physique>
.header {
place: sticky;
high: 0; /* Offset, in any other case it will not stick! */
}
/* and so forth. */
With these declarations, the .header will stick with the highest of the web page. And but the .hero aspect beneath it stays intrinsically sized. That is what we need to change.
The Low-Hanging Fruit
The primary impulse you might need, as I did, is to surround the header and hero in some type of father or mother container and provides that container 100vh to make it span the viewport. After that, we might use Flexbox to distribute the youngsters and make the hero develop to fill the remaining area.
<physique>
<div class=”container”>
<header class=”header”>Header Content material</header>
<part class=”hero”>Hero Content material</part>
</div>
<principal class=”principal”>Fundamental Content material</principal>
</physique>
.container {
top: 100vh;
show: flex;
flex-direction: column;
}
.hero {
flex-grow: 1;
}
/* and so forth. */
This appears right at first look, however watch what occurs when scrolling previous the hero.
See the Pen Try #1: Container + Flexbox [forked] by Philip.
The sticky header will get trapped in its father or mother container! However.. why?
For those who’re something like me, this habits is unintuitive, no less than initially. You will have heard that sticky is a mixture of relative and stuck positioning, that means it participates within the regular circulation of the doc however solely till it hits the sides of its scrolling container, at which level it turns into fastened. Whereas viewing sticky as a mixture of different values generally is a helpful mnemonic, it fails to seize one vital distinction between sticky and stuck components:
A place: fastened aspect doesn’t care concerning the father or mother it’s nested in or any of its ancestors. It’s going to escape of the traditional circulation of the doc and place itself straight offset from the viewport, as if glued in place a sure distance from the sting of the display.
Conversely, a place: sticky aspect will likely be pushed together with the sides of the viewport (or subsequent closest scrolling container), however it’s going to by no means escape the boundaries of its direct father or mother. Effectively, no less than should you don’t rely visually transform-ing it. So a greater approach to consider it may be, to steal from Chris Coyier, that “place: sticky is, in a way, a domestically scoped place: fastened.” That is an intentional design determination, one that permits for section-specific sticky headers like those made well-known by alphabetical lists in cellular interfaces.
See the Pen Sticky Part Headers [forked] by Philip.
Okay, so this strategy is a no-go for our predicament. We have to discover a resolution that doesn’t contain a container across the header.
Mounted, However Not Solved
Perhaps we are able to make our lives a bit less complicated. As an alternative of a container, what if we gave the .header aspect a fastened top of, say, 150px? Then, all now we have to do is outline the .hero aspect’s top as top: calc(100vh – 150px).
See the Pen Try #2: Mounted Peak + Calc() [forked] by Philip.
This strategy kinda works, however the downsides are extra insidious than our final try as a result of they might not be instantly obvious. You in all probability seen that the header is simply too tall, and we’d wanna do some math to determine on a greater top.
Considering forward a bit,
What if the .header’s kids have to wrap or rearrange themselves at completely different display sizes or develop to keep up legibility on cellular?
What if JavaScript is manipulating the contents?
All of this stuff might subtly change the .header’s supreme measurement, and chasing the suitable top values for every state of affairs has the potential to spiral right into a upkeep nightmare of unmanageable breakpoints and magic numbers — particularly if we take into account this must be accomplished not just for the .header but in addition the .hero aspect that will depend on it.
I might argue that this workaround additionally simply feels mistaken. Mounted heights break one of many principal affordances of CSS structure — the way in which components routinely develop and shrink to adapt to their contents — and never counting on this normally makes our lives more durable, not less complicated.
So, we’re left with…
A Novel Strategy
Now that we’ve discovered the constraints we’re working with, one other solution to phrase the issue is that we would like the .header and .hero to collectively span 100vh with out sizing the weather explicitly or wrapping them in a container. Ideally, we’d discover one thing that already is 100vh and align them to that. That is the place it dawned on me that show: grid could present simply what we want!
Let’s do this: We declare show: grid on the physique aspect and add one other aspect earlier than the .header that we’ll name .above-the-fold-spacer. This new aspect will get a top of 100vh and spans the grid’s total width. Subsequent, we’ll inform our spacer that it ought to take up two grid rows and we’ll anchor it to the highest of the web page.
This aspect should be solely empty as a result of we don’t ever need it to be seen or to register to display readers. We’re merely utilizing it as a crutch to inform the grid find out how to behave.
<!– This spacer offers the peak we would like –>
<div class=”above-the-fold-spacer”></div>
<!– These two components will place themselves on high of the spacer –>
<header class=”header”>Header Content material</header>
<part class=”hero”>Hero Content material</part>
<!– The remainder of the web page stays unaffected –>
<principal class=”principal”>Fundamental Content material</principal>
</physique>
physique {
show: grid;
}
.above-the-fold-spacer {
top: 100vh;
/* Span from the primary to the final grid column line */
/* (Adverse numbers rely from the tip of the grid) */
grid-column: 1 / -1;
/* Begin on the first grid row line, and take up 2 rows */
grid-row: 1 / span 2;
}
/* and so forth. */
This is the magic ingredient.
By including the spacer, we’ve created two grid rows that collectively take up precisely 100vh. Now, all that’s left to do, in essence, is to inform the .header and .hero components to align themselves to these present rows. We do have to inform them to start out on the similar grid column line because the .above-the-fold-spacer aspect in order that they received’t attempt to sit subsequent to it. However with that accomplished… ta-da!
See the Pen The Answer: Grid Alignment [forked] by Philip.
The rationale this works is that a grid container can have a number of kids occupying the identical cell overlaid on high of one another. In a state of affairs like that, the tallest baby aspect defines the grid row’s general top — or, on this case, the mixed top of the 2 rows (100vh).
To regulate how precisely the 2 seen components divvy up the obtainable area between themselves, we are able to use the grid-template-rows property. I made it in order that the primary row makes use of min-content moderately than 1fr. That is essential in order that the .header doesn’t take up the identical quantity of area because the .hero however as an alternative solely takes what it wants and lets the hero have the remaining.
Right here’s our full resolution:
physique {
show: grid;
grid-template-rows: min-content 1fr;
}
.above-the-fold-spacer {
top: 100vh;
grid-column: 1 / -1;
grid-row: 1 / span 2;
}
.header {
place: sticky;
high: 0;
grid-column-start: 1;
grid-row-start: 1;
}
.hero {
grid-column-start: 1;
grid-row-start: 2;
}
And voila: A sticky header of arbitrary measurement above a hero that grows to fill the remaining seen area!
Caveats and Last Ideas
It’s value noting that the HTML order of the weather issues right here. If we outline .above-the-fold-spacer after our .hero part, it’s going to overlay and block entry to the weather beneath. We are able to work round this by declaring both order: -1, z-index: -1, or visibility: hidden.
Remember that it is a easy instance. For those who have been so as to add a sidebar to the left of your web page, for instance, you’d want to regulate at which column the weather begin. Nonetheless, within the majority of circumstances, utilizing a CSS Grid strategy is prone to be much less troublesome than the Sisyphean process of manually managing and coordinating the peak values of a number of components.
One other upside of this strategy is that it’s adaptable. For those who determine you need a group of three components to take up the display’s top moderately than two, you then’d make the invisible spacer span three rows and assign the seen components to the suitable one. Even when the hero aspect’s content material causes its top to exceed 100vh, the grid adapts with out breaking something. It’s even well-supported in all trendy browsers.
The extra I take into consideration this system, the extra I’m persuaded that it’s really fairly clear. Then once more, you understand how attorneys can discuss themselves into their very own arguments? For those who can consider a fair less complicated resolution I’ve neglected, be at liberty to achieve out and let me know!
Subscribe to MarketingSolution.
Receive web development discounts & web design tutorials.
Now! Lets GROW Together!