The pain of building nested grids
Jan 13, 2022
It feels ungrateful to complain about building layouts in 2022, since CSS has advanced so much, but you gotta do what you gotta do.
Layouting a website is not an easy task, even with the help of the CSS greats: ✨ Flexbox ✨ and ✨ Grid ✨. Although those two offer you a variety of solutions for different problems, you still have a lot of thinking to do when it comes to consistently positioning elements.
Today, we will verify how difficult it is to maintain the same grid across multiple components of various HTML depths.
Useful links:
1. The problem
Let's say we want to build a grid consisting of two equal columns: A and B. The B column, however, contains another grid with 1:2 ratio (named, respectively, B1 and B2). To wrap our heads around it easier, let's draw it:
We built our grid with 12 columns in mind, so if we were to outline them on top of our layout, we would get:
Both A and B get 6 columns each, but B also splits to B1 (2 columns) and B2 (4 columns)
Achieving it in CSS is nothing demanding, but it teases a problem that will be common across this entire piece. Let's do a quick recap of how does CSS Grid work to learn what the problem is.
CSS grid offers two sets of properties accessible from two entry points: one to set on a parent level, and another to utilize on a children level.
It goes like this - we declare the details of our rows and columns:
.grid {display: grid;grid-template-columns: 1fr 1fr 1fr 1fr 1fr;}
and then if we have some specific demands, we can set it up for each individual grid child:
.grid-item:first-child {grid-column: 1 / 4;}
In our case, however, the latter is not necessary. A grid of two equal columns (A + B) could look like this:
.main-grid {display: grid;grid-template-columns: 1fr 1fr;}
Implementing the inner grid of B1 and B2 is nothing challenging either:
.sub-grid {display: grid;grid-template-columns: 1fr 2fr;}
We were lucky to choose a ratio for our B1 + B2 subgrid that plays nicely with our main grid. If we assume "1fr" from .main-grid
equals to 6 columns, it divides by 3 (1 + 2, from .sub-grid
's grid-template-columns
property).
We can't say we benefit fully from a grid design, if each time we go deep down one level, we have to manually count how many columns we have on our disposal. The ratio from our example would go down the drain as soon as we messed up the numbers, just like so:
.sub-grid {display: grid;grid-template-columns: 3fr 5fr; /* <- 3 + 5 = 8; 6 / 8 = ⛔ */}
Anything we established on a parent level, we lose track of the moment we head to a grid child. In a result, we have to babysit each subgrid we create, even though they are all based on a one, primary grid.
The "main-grid" influence spreads only to its direct children: the "grid-items".
We want the subgrid children to participate in the main grid, but it has no power here.
Well, the ratio just won't change on its own, right? You must willingly allow it, and that may seem unlikely in your scenario. That, however, proves that our subgrid is not a solid construction that truly determines the layout for all its children, unless our HTML is completely flat. We are doomed to live the life of a grid watchman, making sure that the ratio of columns we set on a parent level is being restricted on a children level.
2. Current solutions
2.1 display: contents
The remedy for flattening our HTML while building more complex grids can be found (in specific cases) in display: contents
property. What it does is that it makes the element hold a purely semantical value, that gets ignored in terms of display, exposing its children instead.
Don't know what the hell am I talking about? I hope this example will suffice!
Felt frisky and peeked into the dev tools anyway? This is what you might have seen there:
Did you notice how there is no box for the pass-grid
div? This is the superpower that display: contents
gives us: it passes on the display properties.
We can say that display: contents
employs an element as a layout proxy. The job is so taxing, an element can't do anything else at the same time, meaning all the other styling properties get canceled from the element the moment we set display: contents
on it.
.layout-proxy {background: red; /* <- this works! */}
.layout-proxy {background: red; /* <- this no longer works */display: contents;}
As we can see, this is not ideal. It does not solve all the problems you can encounter while developing a complex grid. The only help it provides is with flattening the markup, but even that, to some extent.
2.2 CSS Grid powered by CSS Variables
If we can't summon our main grid anywhere in our markup, the least we can do is store the details of it in CSS Variables, and reuse it:
:root { --main-grid-first-col: 256px; --main-grid-second-col: 2fr; --main-grid-third-col: 3fr; --main-grid-gap: 10px; } .main-grid { display: grid; grid-template-columns: var(--main-grid-first-col) var(--main-grid-second-col) var(--main-grid-third-col); grid-gap: var(--main-grid-gap); } .aligned-grid { display: grid; grid-template-columns: var(--main-grid-first-col) auto; grid-gap: var(--main-grid-gap); }
We can adjust the first column width
This solution proves worthy when trying to sync grids together but it does not directly bind one grid with another, just their dimensions. As far as I know, this is where our CSS possibilities end. At least for now.
3. CSS Subgrid
Hopefully, the life of a front-end developer will become a bit easier when CSS Subgrid truly arrives. For now, the only browser it's available in is Firefox, which means we can sit back and fantasize.
CSS Subgrid is a value of grid-template-columns
and grid-template-rows
that enables the parent to share its grid lines with its children.
We can easily highlight its benefits on the example from the beginning of the article:
Instead of effectively creating two unrelated grids that align with each other by sheer luck, with CSS Subgrid we will be able to do something like this:
<body><section class="main-grid"><div class="grid-a">A</div><div class="grid-b"><div class="sub-grid-item-b1">B1</div><div class="sub-grid-item-b2">B2</div></div></section></body>
.main-grid {display: grid;grid-template-columns: repeat(12, 1fr);}.grid-a {grid-column: 1 / 7; /* <- where "7" is the end of 6th column */}.grid-b {grid-column: 7 / 13; /* <- where "13" is the end of 12th column */display: grid;grid-template-columns: subgrid;}.sub-grid-item-b1 {grid-column: 1 / 3; /* <- where "3" is the end of 2nd column */}.sub-grid-item-b2 {grid-column: 3 / 7; /* <- where "7" is the end of 6th column */}
💻 Click here, if you have Firefox installed.
The ability to refer the .main grid
's dimensions from the sub-grid-item
is a true gamechanger. This opens the door to implementing and enforcing a one true grid system across the entire layout, not just create a bunch of grids that try to work together.
There is a one limitation of CSS Subgrid, though. It does not give us a way to declare a main grid, and then reference it at any depth of our markup. At some point, we would like to break the cascade...
CSS Grid wishful thinking
...but that's not possible. It's not unusual, though, because CSS only takes care of positioning the elements in relation to each other. To achieve it, we would have to track the state of our main grid across the entire application, and react when something's changed.
4. JavaScript
Since we are so good at fantasizing, let's do it one last time.
A perfect grid solution would allow us to:
- enforce one grid across the entire layout
- maintain the state of the grid, and prevent exceeding the defined number of columns/rows (with CSS Grid nothing will stop us, if we order an element to span from the column 1 to 14, even if we defined our grid as 12 columns)
- position an element as a part of the grid, without worrying about the depth of HTML and passing the grid properties via
subgrid
None of these points are currently achievable with just CSS and HTML, nor will they be with an addition of CSS Subgrid. Don't get me wrong - CSS Subgrid will definitely make our lives significantly easier, but some pains of building a complex, universal grid will remain.
However, as we've learned on numerous occasions, there are no pains in the front-end development JavaScript can't solve...
import { useGrid } from "@peelar/react-grid";
Make sure not to miss an update on that!
You are not going to believe those 😱: