Stop creating breakpoints for your grid layouts

Last updated on 17 May 2026

If you are here just for the magic CSS sauce, then copy the below styles to your div that you want to grid and you are on your way. You can tweak the --max-columns , --min-column-size, --gap values according to how you want to set up your grid.

.grid {
  --max-columns: 4;
  --min-column-size: 275px;
  --gap: 1rem;
  --column-size: max(var(--min-column-size), calc((100% - (var(--gap) * (var(--max-columns) - 1)) / var(--max-columns)));
  
  display: grid;
  gap: var(--gap);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, var(--column-size)), 1fr));
}

If you are using Tailwind and React, then copy the code below.

<div
  className="grid gap-[var(--gap)] [grid-template-columns:repeat(auto-fit,minmax(min(100%,var(--column-size)),1fr))]"
  style={{
	  '--max-columns': 4,
    '--min-column-size': '275px',
    '--gap': '1rem',
    '--column-size': `max(var(--min-column-size), calc((100% - (var(--gap) * (var(--max-columns) - 1))) / var(--max-columns)))`
  }}
>
 // ADD YOUR CHILDREN ELEMENTS HERE
</div>

The calculations can seem very overwhelming at first and if you’re here to learn how all this works, keep reading. I learned this trick from one of the best CSS educators, Kevin Powell, so full credit goes to him. If you’re more of a visual learner, then check out this and this video that he made. He explains this technique far better than I ever will.

How does this work?

At a high level, we are trying to solve one specific problem. How can we create a responsive grid layout without relying on media queries? The trick comes down to three things: repeat(), auto-fit (or auto-fill), and minmax().

repeat(auto-fit, …)

repeat() does exactly what it sounds like. It repeats a column pattern multiple times.

grid-template-columns: repeat(3, ...); /* Creates exactly 3 columns */
grid-template-columns: repeat(auto-fit, ...); /* Creates as many columns as possible. */ 

In the first example, we are telling the browser to create exactly 3 columns. By replacing the number with the auto-fit keyword, we tell the browser to create as many columns as possible while still respecting the minimum width we give you. As the container gets smaller, columns wrap onto the next row automatically. This is where the "breakpoint free" behaviour starts to happen.

minmax()

To tell each column how small they can get, and how large they can grow, we use minmax(). The syntax is

minmax(min-size, max-size)

In our example, we use min() (and max() which is hidden inside var(--column-size)) inside minmax() which makes this look complicated, but we have to do this to make our grid robust. Let’s take it slow.

minmax(min(100%, var(--column-size)), 1fr)

Why min() inside minmax()?

To put it simply, we use min() to never have overflow issues. min() always returns the smaller value. Let’s take an example

min(100%, 275px)

This means, use 275px of the available width, unless the container becomes smaller than 275px. Once the container becomes ≤274px, use 100% of the available width. Without min(), if the minimum column size is larger than the available width, the grid will overflow.

Breaking down the column size calculation

--column-size: max(var(--min-column-size), calc((100% - (var(--gap) * (var(--max-columns) - 1))) / var(--max-columns)));

--column-size is where the actual column size calculation happens. Let’s look at what happens inside calc() first before seeing why we use max().

calc((100% - (var(--gap) * (var(--max-columns) - 1))) / var(--max-columns)));

(100% - (var(--gap) * (var(--max-columns) - 1)) takes the full width of the container and subtracts the total space occupied by the grid gaps. We do var(--max-columns) - 1 because the number of gaps is always one less than the number of columns. For example, 2-column layout has 1 gap, 3-column layout has 2 gaps, and so on.

Once the gaps are removed, we are left with the usable space inside the grid. This space is then divided equally across all columns, which gives us the width of a single column when the grid is at its maximum column count.

Why use max()?

The calculation is then wrapped inside max()

max(var(--min-column-size), calculated-width)

max() always returns the larger value. This means never let columns become smaller than the minimum size we defined. For example

max(275px, 180px)

returns 275px, and once the calculated width becomes smaller than 275px, the browser cannot shrink the column anymore, and it drops to the next row.

So that's it. If you’ve read up to this point, you’ll see there is no magic here. Instead of manually controlling the layout at specific screen sizes, you define the constraints once, do some math, and let the grid respond automatically.