Have you ever had to create a neat little grid of logos on a page? CSS can help you with grid and its
grid-template-columns property. Using
minmax you can say “Take up as much space as possible but I want each item to be between X and Y wide!” Legitimately powerful stuff in its own right.
I have needed this but with a specific number of columns: I want 3 columns when there is space, but I also don’t want to write media/container queries for narrower viewports (nothing wrong with using media queries, mind you, but with the recent advances in CSS, they are not always the best tool for the job). Well, I’ve come up with this little solution that I quite like. First, the code, then a breakdown, and finally a demo:
--_cols: max(1, var(--cols, 3)); /* Ideal number of columns is 3 by default; at least one! */
--_gap: var(--gap, 2.5rem); /* Space between each logo */
--_min: var(--min, 160px); /* Logos must be at least this wide */
--_max: var(--max, 320px); /* Logos cannot be wider than this size */
/* Take up at least --_min, and up to 100%/count (minus value of each added gap), whichever is largest */
grid-template-columns: repeat(auto-fill, minmax(max(var(--_min), calc((100% - var(--_gap) * (var(--_cols) - 1)) / var(--_cols))), 1fr));
I define four pseudo-private custom properties, denoted by an underscore as a prefix (yet another excellent Lea Verou idea). The
.grid element can take overrides with the
style attribute, such as
style="--cols: 4; --gap: 10px;". The grid is set up with
display: grid; and the gap between each item is controlled by the
Now for the interesting part,
grid-template-columns, which looks a bit complex, so let me break it down:
repeatdoes exactly that: repeats the second parameter (
minmax(…)) as many times as indicated by the first parameter (
auto-fill). Here, that is a special keyword to dynamically create the columns depending on their provided size and the available space. You could use
var(--cols)but that won’t adapt to the viewport size.
auto-fitis another valid keyword: in this CSS-Tricks article, Sara Soueidan explains the difference better than I ever could.
minmax(max(…), 1fr)sets the width to be between the first and second value. The first value here is a
max()function I’ll explain in a moment, and the second is
1fr, standing for “one fraction of the available space”.
max(…)uses the greatest value in the provided list, in this case either the
--_minvariable or the
calc()expression (described below). Note that you do not need to wrap this in a
calcfunction when you are already within
clampbut I feel it improves readability (and minifiers will remove this regardless!).
calc(…)is doing the heavy lifting:
100%is the grid’s available width, and if
--_colsis set to
3, I want
100%/3 = 33.333%to be used. But wait, that doesn’t account for gaps… So, just like a good ol’ flex-based grid, I can subtract the gap value from the total available space, keeping in mind that if I have 3 columns (
N = 3), there are 2 gutters (
N - 1), meaning that the formula is
100% - gap-size * (columns - 1), which then gets divided by the number of columns, resulting in the available space for each column:
(100% - var(--_gap) * (var(--_cols) - 1)) / var(--_cols). Who said maths were useless after school?!
This results in a grid that will have up to
--_cols columns, but that will show fewer columns on narrower viewports, depending on
--_gap (or their defaults if omitted). The only way this would break, I believe, would be if
--_min was set to a rather large value. If you need to implement this and don’t trust that value, you can wrap the
max(…) expression in a
min(100%, max(…)). I’d reach for
clamp but it is not exactly the same since
clamp(var(--_min), calc(…), 100%) does not evaluate to the same result, as
--min could be wider than
100% — watch out for those kinds of traps!
There is a little more going on for each grid item and its logo (
<svg>), leveraging those
--_max variables, but they do not define the column sizes so I’ve left them out from the code above, but you’ll find all them in the CodePen demo. Here’s how that looks in action (if your browser supports grid, custom properties and max functions, that is — I’ve removed flex and var-less/min-max-less fallbacks for conciseness, but always favour progressive enhancement in client work!):
See the Pen
This is one of many possible ways to make this work, and I have likely over-engineered this, so if you have ideas to simplify this code, I’d love to know. In the meantime, keep on making cool things!