← Back to blogYouTube Video

October 24, 2026 • By

CSS grid auto-fit and minmax() without media queries

Can't play the video or having issues? Here's the direct link.

The short version

  • repeat(auto-fit, minmax(300px, 1fr)) makes a responsive CSS grid without media queries: the browser fits as many columns as it can at the minimum width and the 1fr maximum stretches them to fill the row.
  • Wrap the minimum width in min(..., 100%) so a single column can shrink to the container's width on screens narrower than the minimum, instead of overflowing.
  • A minimum of max(300px, 100% / 4) gives three columns on wide screens even with gaps, and never more than three. With 100% / 3 the gaps leave room for only two.
  • auto-fit collapses empty columns so the items stretch across the row, while auto-fill keeps the empty columns. They only differ when there are fewer items than columns.
  • Make items span two columns only inside a media query. In a one-column grid, an item that spans two forces an extra column and the page scrolls sideways.

To make a responsive CSS grid without media queries, set grid-template-columns to repeat(auto-fit, minmax(<minimum width>, 1fr)). The browser fits as many columns as it can at that minimum width, stretches them to fill the row, and drops to fewer columns as the screen gets narrower.

Below I build a four-card feature section with that one line, then go through each piece of it: repeat(), auto-fit, minmax(), max() and min(). The only media query left makes two of the cards span two columns. The code is the React and Tailwind component from the video above, and there's a plain CSS version too.

The layout

The section has four cards. On a wide screen there are three columns: the first card spans two of them and the second card sits next to it, then the next row flips that, with the third card on its own and the fourth spanning two. As the screen gets narrower it drops to two columns, and on a phone every card stacks.

You've probably seen layouts like this one on landing pages. I build it with CSS grid and a few CSS functions, and the grid works out the number of columns by itself.

The one line that does the work

This is the CSS for the grid's columns:

grid-template-columns: repeat(auto-fit, minmax(min(max(300px, 100% / 4), 100%), 1fr));

It looks complicated, but it's four functions and a keyword nested inside each other. I'll go from the outside in.

repeat() and auto-fit

repeat(count, size) creates the same column size over and over. Normally the count is a number, like repeat(3, 1fr). Pass auto-fit instead and the grid creates as many columns as fit in the container, based on the size you give it. That's where the responsiveness comes from.

minmax() and 1fr

The size is minmax(minimum, maximum). Every column has to be at least the minimum wide, and can grow up to the maximum.

The maximum here is 1fr, one fraction of the free space. Once the grid has worked out how many columns fit, the columns share whatever space is left over equally, so the row is always filled edge to edge. If you put a fixed size there instead, the columns stop growing at that size and leave a gap at the end of the row.

Everything else in that line is the minimum column width.

max(300px, 100% / 4)

max() takes two values and returns whichever is larger. Here it's 300px or a quarter of the container's width.

On a big screen, a quarter of the container is more than 300px, so the minimum column width is 25%. On a smaller screen a quarter drops below 300px, so the minimum becomes 300px and you get fewer, wider columns.

I divide by 4 because the layout needs three columns: two for the first card and one for the card next to it. You'd think 100% / 3 would give three columns, but the grid also has a 2rem gap between columns, and three columns at a third of the width each plus two gaps is wider than the container. The browser then fits only two. I checked: with 100% / 3 the grid on a 1280px screen has two columns. With 100% / 4 there's room for three columns plus the gaps.

It also caps the grid at three columns, however wide the screen gets. Four columns of at least 25% each plus three gaps can never fit.

min(..., 100%)

The result of max() goes into min(), which returns the smaller of two values: the max() result or 100% of the container.

On a big screen the max() result is always smaller than 100%, because a quarter of the container or 300px is less than the whole container. So min() passes it through and nothing changes.

On a phone, max() returns 300px, and a phone's container can be narrower than that. A 320px screen with 20px of padding on each side leaves 280px. Without min(), the column would still be 300px wide and stick out of the container. With it, the minimum becomes 100% of the container, so the one column is exactly as wide as the screen allows.

I tested both on a 300px-wide screen. With min() the cards are 260px, the width of the container. Without it they're 300px, they stick out 20px past the edge of the screen and the page scrolls sideways.

min() and max() have worked in every major browser since 2020, so you can use them without a fallback.

auto-fit vs auto-fill

repeat() also takes auto-fill, and the two look the same until you have fewer items than columns. auto-fit collapses the empty columns to zero width, so the items stretch across the row. auto-fill keeps the empty columns at their size, so the items stay narrow and the rest of the row is blank.

Here's what happened when I put only two cards in this grid on a 1280px screen:

auto-fitauto-fill
Columns created3, the empty one at 0px3, all 392px
Width of each card604px392px
The rest of the rowFilled by the two cardsOne empty column
Use it whenThe items should always fill the rowThe items should keep the same size

For a section like this one, where the cards should fill the page, use auto-fit. With all four cards in the grid it makes no difference which one you pick.

The one media query

The grid handles the columns, but it can't know that the first and last cards should be wider. That part needs a media query: from 768px up (Tailwind's md breakpoint), the first and last cards span two columns.

@media (min-width: 768px) {
    .benefit:first-child,
    .benefit:last-child {
        grid-column: span 2;
    }
}

The media query matters. Below 768px the grid can have a single column, and a card that spans two columns in a one-column grid makes the browser add a second column that doesn't fit. I tried it on a 375px screen: the browser added a 40px column and the page scrolled sideways.

Here's where the layout changes with this exact code, a 2rem gap and 20px of padding on each side:

Screen widthColumnsWhat you see
Under 672px1Every card stacked
672px to 767px2Two rows of two cards
768px to 1003px2First and last cards full width, the middle two side by side
1004px and up3The full layout

Only the 768px change comes from the media query. The grid works out the other two from the container's width.

The full component

This is the component from the video, in React with Tailwind v4. I tested it in a Vite app with React 19 and Tailwind 4.3.3:

export default function GridLayout() {
    const benefits = [
        {
            img: "https://i.imgur.com/QZ1JVKk.png",
            title: "Track and optimize your sales pipeline",
            text: "Visualize, manage, and streamline your sales process for maximum efficiency and conversion rates.",
        },
        {
            img: "https://i.imgur.com/CEX0jl7.png",
            title: "Stay Organized",
            text: "Assign, track tasks effortlessly to keep your team aligned and productive.",
        },
        {
            img: "https://i.imgur.com/dl6YGru.png",
            title: "Contact Information",
            text: "Access detailed contact information at a glance.",
        },
        {
            img: "https://i.imgur.com/JdliMW3.png",
            title: "Efficient Contact Management",
            text: "Centralize and organize all your contacts for easy access and effective communication.",
        },
    ];

    return (
        <section className="py-20 bg-white">
            <div className="max-w-7xl mx-auto px-5">
                <div className="grid grid-cols-[repeat(auto-fit,minmax(min(max(300px,100%/4),100%),1fr))] gap-8">
                    {benefits.map((benefit, i) => (
                        <div key={i} className="p-5 rounded-xl shadow bg-gray-50 md:first:col-span-2 md:last:col-span-2 overflow-hidden">
                            <h4 className="text-3xl font-semibold mb-6">{benefit.title}</h4>
                            <p className="text-gray-500">{benefit.text}</p>
                            <div className="mt-8">
                                <img src={benefit.img} alt="" className="w-full rounded-lg border border-gray-300" />
                            </div>
                        </div>
                    ))}
                </div>
            </div>
        </section>
    );
}

The section adds vertical padding and a white background. The div inside it is a container, capped at 1280px with max-w-7xl and centered with mx-auto. Its px-5 is the 20px of padding on each side that the widths in the table above account for.

The grid is the next div. Tailwind's arbitrary value syntax, the square brackets after grid-cols-, lets you put any value into grid-template-columns. Class names can't contain spaces, so the value is written without them. Tailwind builds it into the same CSS as above, and even simplifies 100%/4 to 25%. gap-8 is the 2rem gap.

Each card gets md:first:col-span-2 and md:last:col-span-2, which is the media query from the last section in Tailwind form. first: and last: are the :first-child and :last-child selectors, so only the first and last cards span two columns, and only from md up. overflow-hidden clips anything too wide for a card, like a long URL, so it doesn't spill over the card next to it.

The same grid in plain CSS

If you're not using Tailwind, here's the grid on its own:

<section class="benefits">
    <div class="benefits-grid">
        <div class="benefit">Track and optimize your sales pipeline</div>
        <div class="benefit">Stay organized</div>
        <div class="benefit">Contact information</div>
        <div class="benefit">Efficient contact management</div>
    </div>
</section>
.benefits {
    max-width: 80rem;
    margin: 0 auto;
    padding: 5rem 1.25rem;
}

.benefits-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(max(300px, 100% / 4), 100%), 1fr));
    gap: 2rem;
}

.benefit {
    padding: 1.25rem;
    border-radius: 0.75rem;
    background: #f9fafb;
    overflow: hidden;
}

@media (min-width: 768px) {
    .benefit:first-child,
    .benefit:last-child {
        grid-column: span 2;
    }
}

I measured both versions at ten screen widths from 300px to 1920px, and they switch columns at the same widths.

Run it

Create a Vite app with React and TypeScript, and add Tailwind with its Vite plugin:

bun create vite@latest grid-demo --template react-ts
cd grid-demo
bun install
bun add tailwindcss @tailwindcss/vite

Add the plugin to vite.config.ts:

import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import tailwindcss from "@tailwindcss/vite";

export default defineConfig({
    plugins: [react(), tailwindcss()],
});

Replace everything in src/index.css with Tailwind's import:

@import "tailwindcss";

Save the component as src/GridLayout.tsx, delete src/App.css, and render the component from src/App.tsx:

import GridLayout from "./GridLayout";

export default function App() {
    return <GridLayout />;
}

Start it with bun run dev and open the URL it prints. On a desktop screen you'll see the three-column layout with the wide first and last cards. Open the developer tools, switch to the responsive view and drag the width down: at 1003px it drops to two columns with the first and last cards full width, at 767px the cards go two by two, and below 672px every card stacks.

Play with the numbers

The nested min() and max() are what make this work. max() keeps the columns from getting too narrow on a big screen, and min() keeps them from overflowing on a small one. Between the two, the grid fills whatever space it has.

It took me a while to see why each piece was there, and reading about it only gets you so far. Recreate this in your own code and change the values. Swap the 4 for a 3 and watch the third column disappear, or take min() out and shrink the window until the cards hang off the side. Once you've watched the columns change, the maths makes a lot more sense.

Video transcript

Show
Alright, so you've probably seen dynamic advanced layouts like this one. I'm going to show you how to create this layout with literally zero media queries. This is all going to be CSS Grid and some cool CSS functions. You just need one media query and that's just to make this first and last item, like this one and this one right here, take up two columns instead of one to achieve this layout. Everything else we're going to do with CSS Grid and some responsive functions. media queries. So to get started with this let's first go to my code here and you can see I already have a list of benefits to render over here. Let's start to add the code over here. So first things first I'm going to rename this div into a section and I'm going to give it some classes. I'll give it some padding and a background of white. And inside this, let's create a container. We'll create a div and we'll give it a max width of 7xl which is 1280 pixels. And margin auto. Also, give it some horizontal padding. And in here, now we'll create a grid. and we'll give it these columns. Just copy this exactly as I do it. It's going to look a bit weird, a bit complicated, but I'll explain how all of this works in a bit. And we'll also give some gap over here. Now these columns do look a lot complicated and I will explain this in just a bit. But first let's create this whole layout. Now inside of this div, in this grid, let's render the benefits list that we have. This will be a div and it will have the key and it will have these classes. Some padding, it will be rounded and have a shadow and a background of grey 50. And we will also make it so that on the empty breakpoint the first and last items in this grid will take up two columns instead of one And this will give us the layout that we want So do the same thing for the last item. And also overflow hidden so we don't have any overflow issues with this. Now inside of this let's render the actual content for each benefit. I'll create an h4 and I'll give it the classes. text 3xl and semi bold font and some margin bottom and here we'll render the title of this and right below this let's create a paragraph with gray 500 and here we'll render the benefit text Now below this let's also render the image and I'll give it some margin top just to separate it from the text and in here let's create an image component and render the image right here. And let's also give it some classes to style this. We'll give it full width and make it rounded. Give a border of gray 300 and with that we are done with this now let's actually go and see what this actually looks like so if you can see over here we have actually achieved all of the layout that we wanted it looks exactly like i showed you and if i go to the developer tools and resize this you can see it's actually responsive as well. So on smaller screens it shifts to this layout and if we go even smaller than this then it just shifts to a single column layout. So all of this is fully responsive and we've achieved the layout that we wanted. So how exactly does this work? Let's go through this step by step. So first of all, we have a section over here. This is just standard padding background. This is nothing complicated. Inside we have a div, which is basically a container for all of our content. I gave it a max width and I gave it some margin auto to center it. So we have a container and a max width for the entire section. And in here we have our grid. And this is where all the magic is happening. So if we go inside this div and also before we discuss the grid we need to look at this media query over here inside the benefits where we render each benefit. What we're doing over here is we taking the first and the last item in this grid and we making it so that it spans two columns instead of one on medium screens and above which is exactly what gives us the layout that we want So over here you can see the first and the last items take up two columns instead of one and that's what gives us this layout and if I shrink below the MD breakpoint which is at 768 pixels if I resize this all the way down to 768 and below right here you'll see that the first and last items are now taking up only one column instead of two now the most important part of this whole layout is this class right here which defines the grids columns and this looks very complicated in as a tailwind class so I'm going to turn this into CSS and then we're going to see how it actually works so this is the CSS that this class right here will generate. So let's look at this. First we have the repeat function and this just tells our grid to repeat as many columns as it can and then inside this we have the auto fit keyword which there we go the auto fit keyword and this just gives us the responsiveness that we want. This auto fit keyword is going to try and fit as many columns as it can based on the minimum and maximum column size that we define later in this min max function and now min max is going to take two arguments which is the minimum column size and the maximum column size the maximum size that we give this over here is one fr which is just to make each column take up as much space as it fractionally can. And this whole function right here is the minimum size that each column can be. So inside of this min function right here we have another max function. So first let's look at that one inside. This is also going to take two arguments and it's just going to return whichever is larger. That's why it's called max. So we're saying 300 pixels or 100% of the screen divided by 4. And why 4 over here? Because we need at least 3 columns to create this layout that we want. If I show you the layout again, you can see we need at least 3 columns to make this whole layout. We need two columns for this first element and at least one column for this second element same thing down here so we need at least three columns for this layout and We also have a bunch of gap between each Column which is why we want to give this four columns right here Just to accommodate the gap if we didn have the gap this would be 100 divided by 3 So that why we divide the screen or divide 100 by 4. Now what this max function is doing is it's saying if 300 pixels is larger than 100% of the screen divided by 4 then give us 300 pixels. Otherwise give us 100% divided by 4. So on large screens 100% divided by 4 is bigger than 300 pixels which means that's the value we're going to get on large screens but on smaller screens we're going to get this 300 pixel value now the result of this max function is what gets passed into this min function here outside and the main function just does the same thing as the max but it gives us the minimum value instead of the maximum value. So it takes two values and it returns the minimum value out of those two values. Now on large screens whatever value we get from this max function is automatically going to be smaller than 100 percent because any value you get from over here is just going to be smaller than 100 percent of the screen right. So by default on large screens the max functions value that we get over here this will be the minimum value and this is what will be ultimately passed into the min max function as the minimum column size but on small screens if we look at the max function 300 pixels is going to be bigger than 100 percent of the screen divided by 4 so we're going to get 300 pixels from the max function here on small screens then that 300 pixel in this min function is going to be greater than 100 percent of the screen if we're on mobile or small screens so that will become the minimum value for our column size 300 pixels on small screens and this will just avoid the um what is it the overflow issues that we may run into because then we'll get 100 of the screen as the minimum column size instead of a hard-coded value like 300 pixels. And this will just help us avoid any nasty overflow issues and make our site fully responsive. And that's about it. On small screens, the 100% becomes the column size. And on larger screens, this max functions result becomes the minimum column size. and that's how this whole layout works. Now hopefully I explained everything clearly but I would encourage you to recreate this in your own code somewhere and just play around with the values and see what you get. Experiment with the math here and then you'll have a much better and clearer understanding of everything that I just said.

Share this article

Hassan Aziz pointing up

Dude, you’re at the bottom of my landing page.

Just book a call already if you’re that interested.

You scrolled all the way here.