CSS Grid has been the new, cool kid on the block for a few years now. Grid, with its layout algorithm, has given us so many more capabilities in how we can layout and arrange content in our applications.
CSS Grid allows us to define layout patterns in concise and refined ways. That being said, we simply cannot look at CSS Grid by itself without knowing how other key features of modern CSS (like Math expressions) work with CSS Grid. This can allow us to push the boundaries and create more efficient layouts.
These new layout superpowers enable us to start to move past the constraints and limitations that media queries have.
The Dream of Component Queries
Since the dawn of the first web page layout, we have always looked at web pages from a document/page level. Recently, however, the community at large has started moving toward a component-centric view.
These days, there is more emphasis on style systems, so we now concentrate on creating components. The components we create live in a world that has no concept of pages and no real context of where they will live. Therefore, components should be able to adapt to any space we add them to.
With this shift in paradigm, we need to have our components sized according to the context they are added to. This is where media queries fall short. Media queries themselves only take into context the viewport/page when making adjustments.
In this post, we will dive into the idea of sizing components based on a set of rules. Instead of the page dictating this set of rules, the components themselves define these choices. These rules can be thought of as stepping stones to component queries. While we do not have component queries at this time, we can try to pseudo implement them with the tools and techniques that are currently at our disposal.
Oh No, Please Not Math!
Don’t fret, we aren’t diving back into anyone’s dreaded calculus or trigonometry class from high school. Instead, we’re going to take a look at some of the tools we have in CSS Values 4. We’ll focus mainly on the newer CSS Math expressions and how they can be used effectively with CSS Grid.
We should all be acquainted with our old friend calc()
. If you are not, it is a part of the CSS mathematical expressions. A couple of friends have joined its side, and they are clamp()
, max()
and min()
. These new expressions allow us to do more complex calculations and also to write some of the more complex calc()
in a concise way.
Let’s take a brief look at how this works.
CSS Max Expression
If we reference the good old MDN docs, we can see that the definition for max()
is:
The
max()
CSS function lets you set the largest (most positive) value from a list of comma-separated expressions as the value of a CSS property value. Themax()
function can be used anywhere alength
,frequency
,angle
,time
,percentage
,number
, orinteger
is allowed.
How crystal clear and understandable is that?! Maybe it is for some, but I always like to see an example. Based on the above definition, if we go with something like this:
height: max(5vh, 100px);
What we did above actually says that the element should be 5vh
, but can’t be smaller than 100px
. So, we could write it in longhand as:
width: 5vh; min-width: 100px;
CSS Min Expression
Now, on the other side of the coin, if we look for the definition of min()
in our handy dandy MDN docs, we see:
The
min()
CSS function lets you set the smallest (most negative) value from a list of comma-separated expressions as the value of a CSS property value. Themin()
function can be used anywhere alength
,frequency
,angle
,time
,percentage
,number
, orinteger
is allowed.
Again, let’s break this down with a little bit of sample code, so we can easily understand it.
height: min(50vh, 135px);
What we are saying above is that the element should at most be 135px
, but it should be smaller if the viewport is less than half the viewport height. If we were to re-write that in long hand, it would look like:
max-width: 135px; width: 50vh;
CSS Clamp Expression
Now, if we look at the last math expression clamp()
, we can see that according to MDN, it is defined as:
The
clamp()
CSS function clamps a value between an upper and lower bound.clamp()
enables the selecting of a middle value within a range of values between a defined minimum and maximum. It takes three parameters: a minimum value, a preferred value, and a maximum allowed value. Theclamp()
function can be used anywhere alength
,frequency
,angle
,time
,percentage
,number
, orinteger
is allowed.
For clarity, let’s break it down with an example:
width: clamp(100px, 40vw, 300px);
For this example, we are allowing the element to stretch to 40% of the viewport width, not letting it shrink below 100px
on smaller screens, lastly, telling it not to grow larger than 300px
on wide screens. Cool right?
Get to an Example Already, Geez!
Yeah yeah, I hear you! Let’s put some of what we talked about above into a practical example, so we can see all the voodoo of CSS Grid with CSS Math working together.
We want to use our above math functions with the addition of CSS grid to create a responsive layout for a list of product tiles without using any media queries.
Impossible, you say? Let’s see if I can convince you otherwise.
We’ll go ahead and lay out a few product tiles in our example.
As you can see in that example, we have a collection of product tiles. We are setting the parent of the product tiles to be display: grid
to allow us to use the superpowers of CSS Grid. From there, we are setting grid-template-columns
to be:
grid-template-columns: repeat( auto-fit, minmax(min(100%, var(--product-min-size)), 1fr) );
Here, we are instructing the product tiles’ parent to automatically lay the tiles out in a grid formation. This auto-placement is done using the auto-fit
value in our above snippet.
Additionally, we’re instructing that the minimum size of the tiles should be our declared CSS custom prop (--product-min-size
), which in this case is 17em
. If that 17em
is larger than the parents’ given width, then the title sizes to 100%.
Fairly cool, right? With very little code, we now have a component that adapts its layout based on the size of its context. We go from a minimum size to an expanded size with no media queries.
Hey, What About That Other Expression?
Ok, so let’s see if we can spice this up a bit more by introducing our third expression: clamp()
. Say we want a bit more control over how our auto-fit layout works. Instead of letting the browser automatically fill the layout with tiles as they fit in each row, we want to have exactly two columns when we expand our space.
Normally, if we wanted this amount of control, we would reach into our toolbox and pull out the media queries. However, that wouldn’t be any fun, and I know we can do better.
Let’s take our example from above and specifically move our attention back to our grid column definition:
grid-template-columns: repeat( auto-fit, minmax(min(100%, var(--product-min-size)), 1fr) );
We are telling the browser that it should layout the product tiles across as long as they have enough room to meet our minimum size. So, as long as we expand our space, we will keep adding columns.
But, let’s say we want to only grow to two columns when we expand. What we can do is combine our min()
and max()
to enforce our columns to two by limiting the max size of our columns. This can be done like so:
min(100%, max(50% - var(--products-gutter), var(--product-min-size)))
Here, we are saying that the tile max should be 100% of its parent, that the min should be 50% of the parent, and finally, with that last condition, that the tile needs to be our --product-min-size
.
Note: for the above example, we need to be sure to factor the gutter into our calculation. Otherwise, the browser will not account for it, and we will not have enough space to create our columns.
One other thing to note: we can do arithmetic inside our clamp
, min()
, and max()
without using the normal calc()
expression.
We can improve on this by simplifying the above example by using our clamp()
expression:
clamp(50% - var(--products-gutter), var(--product-min-size), 100%)
As you can see, without the nesting of the min()
and max()
, we get a more concise syntax that is easier to read. In the clamp()
, our first argument is our minimum value of 50% (minus our gutter), our second argument is our preferred value of --product-min-size
, and our last argument is our max
size of 100%.
Can I Even Use This Yet?
Currently, Firefox, Chrome, Edge, and Safari support all three of these math expressions, so yes. You can use these today if you wanted to.
Conclusion
As a developer, it’s up to us to keep up with the ever-evolving landscape of CSS features and how those features can help us work through current UI challenges. With the ability to use CSS Grid in combination with CSS math expressions, we don’t have to rely solely on the viewport for our ability to layout components based on the context they take up.
If you found this post helpful, take a look at one of my blogs on CSS from a few years back on Custom CSS Properties. I would also encourage you to check out the Keyhole Dev Blog! Lots of helpful information on CSS, JavaScript, and much more.