This is just your friendly neighborhood reminder that you can use CSS grid to overlap elements instead of position:absolute
. This has always been a feature of grid (and written about thoroughly!), but I rarely see it used in real life websites. Why is that? Grid was available to use in all major browsers by 2017, so it’s not new. My own excuse for not adopting new patterns and technologies is that old habits die hard.
Rethinking our techniques, however, is sometimes less overwhelming when we scope it to something small.
A real life transformation
We recently added the ability to insert captions under our looping video component on thoughtbot.com. The video component also includes a pause/play button that appears in the bottom right of the video. That button was absolutely positioned relative to a parent element that wrapped both that element and the video.
<figure>
<button>Play/pause</button>
<video autoplay muted loop playsinline src=”/”>
<figcaption>A caption goes here.</figcaption>
</figure>
figure {
position: relative;
}
button {
bottom: 0;
position: absolute;
right: 0;
z-index: 999;
}
video {
display: block;
width: 100%;
height: auto;
}
This code without the figcaption
works perfectly! However, after inserting this new element with the same CSS, our pause/play button overlaps the caption and not the video.
This can be remedied a few different ways, including adjusting the HTML nesting so we can retain the absolute positioning. In this case, the quickest solution appeared to be a minor adjustment to the CSS grid.
figure {
- position: relative;
+ display: grid;
}
button {
- bottom: 0;
- position: absolute;
- right: 0;
+ grid-area: 1 / 1;
z-index: 999;
}
video {
display: block;
+ grid-area: 1 / 1;
width: 100%;
height: auto;
}
By declaring the figure
as display: grid;
and setting the same grid-area
for both the video
and button
element, we’ve instructed both items to sit in the same spot on our grid. The 1 / 1
part in this example is shorthand for grid-column-start: 1
and grid-row-start: 1
, placing an item on the first column and row line. This alone gets us most of the way there, but by default, the button will take up the entire width and height that the video occupies.
Adding place-self: end;
to the button
element puts it in the bottom right corner, just where we want it! place-self
is shorthand for align-self: end;
(which puts the element flush to the bottom of our container) and justify-self: end;
(which puts the element flush to the right of our container).
button {
grid-area: 1 / 1;
+ place-self: end;
z-index: 999;
}
Our button, even though placed over the video, is still within the document flow. Absolute positioning removes elements from the flow, creating an element without height, which subsequent elements may overlap. This isn’t necessarily an issue in this specific case, but keeping elements within the document flow can prevent layout problems, especially when needing to adapt to different viewport sizes.
Grid with pseudo-elements
The ::before
and ::after
pseudo-elements on a display:grid
will also act like child grid items. The Boston Ruby Group website has a header that employs just that, using decorative gradient squares overlaid on a hero image.
<header class=”hero”>
<picture class=”hero__media”>
<source media="(min-width: 80em)" srcset="images.jpg">
</picture>
<div class=”hero__content”>
// headline content
</div>
</header>
.hero {
grid-template-columns: repeat(7, 1fr);
grid-template-rows: repeat(4, 0.5fr);
}
.hero::before,
.hero::after {
content: "";
display: block;
opacity: 0.75;
}
.hero::before {
background-image: linear-gradient(to bottom, var(--color-red), var(--color-teal));
grid-column: 1 / 4;
grid-row: 1 / 4;
}
.hero::after {
background-image: linear-gradient(to bottom, var(--color-purple), var(--color-red));
grid-column: 7 / 8;
grid-row: 3 / 5;
transform: translateY(2.5rem);
}
.hero__media {
grid-column: 2 / 8;
grid-row: 1 / 5;
}
.hero__content {
grid-column: 3 / 6;
grid-row: 4 / 5;
}
Each element in this example has an explicit start and end point along our 7-column, 4-row grid. The ::before
gradient starts at the top left, ending on the 4th row and column. And to really hone in on that overlapped look, the hero image is offset underneath, starting on the second column line.
We can even break out of the grid a bit with the help of a transform. The :after
gradient is pushed down 2.5rem below the last grid row line with the help of translateY
.
But can I still use position:absolute
?
You do you, friend. The lovely thing about CSS and web development in general is that there isn’t usually one right way to achieve something. CSS, especially today, provides us with a lot of flexibility and creative license when it comes to layout decisions. You don’t necessarily have to adopt a new way of doing things if your tried and true pattern works well enough.