Sticky CSS Grid Items
If you’ve ever tried to put a sticky item in a grid layout and watched the item scroll away with the rest of the content, you might have come to the conclusion that position: sticky
doesn’t work with CSS Grid. Fear not! It is possible to get these two layout concepts working together. All you likely need is one more line of CSS.
The Problem
Let’s start with the root issue you’re likely running into, because it’s not always obvious at first why your grid item isn’t sticking.
Suppose we’re putting together a template layout for articles on various candies. We have our title in the left-hand column of a grid layout, and have set the title to be sticky:
article {
display: grid;
grid-template-columns: 20em 1fr;
grid-gap: 4em;
}
.title {
position: sticky;
top: 2rem;
}
Unfortunately, our title isn’t sticking to the top of the viewport, but instead scrolls away with the rest of the text:
See the Pen Blog post > grid + sticky > demo 1 by Melanie Richards (@somelaniesaid) on CodePen.
If we throw a border on our sticky title (or use a CSS Grid inspector), our root issue becomes more obvious:
See the Pen Blog post > grid + sticky > demo 2 by Melanie Richards (@somelaniesaid) on CodePen.
The CSS Grid item sizing algorithm has effectively sized the grid item (our sticky title) to fill the height of the grid slot it has been placed in. We don’t see any sticky effects because its computed height is the same as the content in the grid slot beside it.
Nitty gritty details on sizing
Things are about to get nerdy, skip to “How to fix it” if you just want the solution!
Because we didn’t use any CSS properties that specify how to size our grid items (we will get to these in a minute!), CSS Grid defaulted to its “normal” sizing algorithm when determining the sticky title’s size.
Our title, an h1
…
- Is not a replaced element, such as an
img
,video
,embed
,iframe
, etc. These have special sizing rules. - Does not have a “preferred aspect ratio”. Such an intrinsic aspect ratio can be set to particular elements by the user agent’s stylesheet, or by using the
aspect-ratio
property.
…therefore, the algorithm fell back to a “stretch” sizing scheme. This basically ensures that the box’s size in this dimension is set to fill the layout container it’s in (to simplify, the grid slot).
How to fix it
One way to address this is to specify an extrinsic height dimension for our sticky title, e.g. height: 10em
, max-height: 50vh
, whatever. Specifying such a literal height for our content is hacky though—we don’t want a specific height, we want the computed height to be just enough to accommodate the element’s contents. Instead we can change how this element is aligned to the grid slot.
We can specify one of the following:
align-self
: this property, when specified on the sticky title, tells the element how to align itself to the block direction of the grid slot it’s in.align-self: start
would work nicely in our example, as it will align the item to the “start” edge of the grid slot (in this context, the top edge).align-items
: this property, when specified on thearticle
(our grid parent), tells each grid item how to align itself to the block direction of the grid slot it’s in.
(Note: the place-self
and place-items
shorthands will also work; these handle both block and inline axes)
It might be nice to align the text baseline of our sticky title and the text baseline of our first paragraph. If we set align-items: baseline;
on the article
, that forces the CSS Grid sizing algorithm to use a “fit-content” scheme. There’s some complexity there, but in our case, the algorithm used our grid item’s “max-content size”, or “the smallest size the box could take in that axis while still fitting around its contents”.
Because the sticky title is now sized according to its contents, we get the position: sticky
behavior we’re after:
See the Pen Blog post > grid + sticky > demo 3 by Melanie Richards (@somelaniesaid) on CodePen.
Our updated styles:
article {
display: grid;
grid-template-columns: 20em 1fr;
grid-gap: 4em;
/* The new line we added! */
align-items: baseline;
}
.title {
position: sticky;
top: 2rem;
}
And that’s it! Hope this helps you out of a sticky situation. 😏