The websites I develop usually contain a page builder that I can extend with custom blocks on a per site basis. All these blocks will vertically be spaced using Stacks. Horizontally each block was traditionally wrapped with a class called fluid-container
. This custom class had a width
of 100%
and a max-width
tied to the Tailwind screens sizes. It’s margin
was set to 0 auto
to center the container and lastly it had some padding
on the sides to keep it from touching the device edge.
The problem
Within the fluid container I would typically use a grid that on larger screens existed out of 12 columns. Throughout the website I would use the same gap sizes to get visual vertical rhythm. All good thus far. Things got tricky when I wanted to start an image on column 7 but let it break out of the container and extend to the browser edge. The same would go for, let’s say a visual in a hero section, that would start at the browser edge and line up on the right side with column 6. For these sorts of layouts I had to resort to hacky CSS solutions that never felt great.
The solution
Marco Rieser was busy on a solution based upon this article by Ryan Mulligan and asked me if I would be interested in shipping this solution with my Starter Kit Peak. After playing around I got very excited and set out to simplify the solution and to get it working with Tailwind CSS, within the context of Peak.
The CSS responsible for the grid is tucked a way in a JS based config file, but compiled to CSS it would like something like this.
1.fluid-grid { 2 --col-gap: clamp(1rem, 3vw, 4rem); 3 --content-max-width: theme('screens.xl'); 4 5 --padding-left: clamp(calc(env(safe-area-inset-left, 0rem) + 1rem), 2vw, calc(env(safe-area-inset-left, 0rem) + 2rem)); 6 --padding-right: clamp(calc(env(safe-area-inset-right, 0rem) + 1rem), 2vw, calc(env(safe-area-inset-right, 0rem) + 2rem)); 7 --col-width: calc((min(calc(100% - var(--padding-left) - var(--padding-right) - 2 * var(--col-gap)), var(--content-max-width)) - 11 * var(--col-gap)) / 12); 8 --side-width: minmax(0, 1fr); 9 10 display: grid;11 column-gap: var(--col-gap);12 grid-template-columns:13 [full-start] var(--side-width)14 [content-start col-1] var(--col-width)15 [col-2] var(--col-width)16 [col-3] var(--col-width)17 [col-4] var(--col-width)18 [col-5] var(--col-width)19 [col-6] var(--col-width)20 [col-7] var(--col-width)21 [col-8] var(--col-width)22 [col-9] var(--col-width)23 [col-10] var(--col-width)24 [col-11] var(--col-width)25 [col-12] var(--col-width) [content-end]26 var(--side-width) [full-end];27}
The CSS responsible for rendering the grid.
There are basically two configurable custom properties: --col-gap
and --content-max-width
. With the first one you can set the clamp values for how wide the gaps between columns should be and with the second one you define the max width of the grid container. In this case we use screens.xl
, which would be 1280px
if you didn’t alter your Tailwind screen sizes.
When the screen is smaller than 1280px
we want some padding on the sides. For this we also use clamp with a minimum value of the left or right safe-area-inset
+ 1rem
and a max value of the safe-area-inset
+ 2rem
. This makes sure the padding scales beautifully proportionally to the screen width, since we used 2vw
as the ideal clamp value.
With the padding all set we can calculate the --col-width
. The calculation for this is as follows:
Automatically choose the minimum value (using min()
) between:
100%
---padding-left
---padding-right
-2
*--col-gap
and--content-max-width
Take that value and:
subtract
11
*--col-gap
anddivide by
12.
Lastly, the --side-width
is the remaining space between the grid and the browser edge. That will be evenly distributed on both edges using minmax(0, 1fr)
. The minimum space will be 0
, and the maximum space will be 1fr
, since the total remaining space accounts for 2fr
.
Using min()
, clamp()
and calc()
we can live calculate the column width. And since the --col-gap
and both --padding-*
values are a clamp value based on the screen width it all scales beautifully when you resize the browser.
The end result is a grid that looks like this.
The left and right side would be the browser edges and the first columns the 1fr
space between. Centered within we see the 12 column grid.
Using the grid
You can use custom utilities or arbitrary values to place items on the grid. E.g: md:col-start-[col-3] md:col-span-8
to let an item start on column 3 and span for 8 columns.
Besides using arbitrary values, the following utilities are present by default to span items on the grid. By default in Peak those are used on Bard sets like: text, image, table, video and pull quote and they can be customised on a per-site basis. On this site, this very text is space-md
, and the grid preview you saw earlier is spaced using space-xl
, which makes it break out of the text container on larger screens.
1.span-content .span-md, .span-lg, .span-xl { 2 grid-column: content 3} 4.span-full { 5 grid-column: full 6} 7@media screen('md') { 8 .span-md { 9 grid-column: col 3 / span 810 }11 .span-lg {12 grid-column: col 2 / span 1013 }14 .span-xl {15 grid-column: col 1 / span 1216 }17}18@media screen('lg') {19 .span-md {20 grid-column: col 4 / span 621 }22 .span-lg {23 grid-column: col 3 / span 824 }25 .span-xl {26 grid-column: col 2 / span 1027 }28}
Some default utilities to easily place content onto the grid.
In action those utilities look like this.
Note: the individual blocks (or wrappers) are spaced using the new stack utilities. The full width block got a no-space-y
class to make sure it’s flush against its direct siblings. Read more on how that works in the article about stacks.
Subgrids
If you want to use nested grids that fall onto the outer fluid-grid
, you can use CSS Subgrid to accomplish this. That will look like this:
1<section class="fluid-grid">2 <div class="span-content grid grid-cols-subgrid">3 Children will fall on a new 12 column element that aligns with the parent fluid grid using subgrid.4 </div>5</section>
In this case all children of span-content
will automatically align with the columns of the parent grid.
If you don’t want to use the CSS Subgrid spec yet, Peak exposes the Custom Property col-gap
to the Tailwind spacing scale. This means you can use this value on every utility that uses the spacing scale. For example on width
, height
and margins
. But in this case we can use it as a gap
value to avoid using Subgrid.
1<section class="fluid-grid">2 <div class="span-content grid grid-cols-12 gap-fluid-grid-gap">3 Children will fall on a new 12 column element that aligns with the parent fluid grid.4 </div>5</section>
In conclusion
I’ve developed a few websites using the fluid grid now and I love it. It supports every layout I’ve ever needed. However, it’s not there to make your life more difficult, and you don’t have to use the grid rigidly in every spot of your website. You could easily place all the content of your page block within span-content
. Within you block, just add a new element and define it a completely new grid for your current use case.
Feel free to reach out to me on Discord or mail if you have any questions. If you want to know how the live code examples on this entry work, read this post.
Photo by Pavel Nekoranec on Unsplash.