Web Development
August 20, 20266 min read3 views

Building Responsive Layouts with Tailwind CSS Grid

How I stopped nesting flexbox containers and started building layouts with Tailwind's grid utilities — including the squished-card bug that shipped on TechHub and the auto-fill pattern that fixed it.

A

Admin User

TechHub Administrator

Building Responsive Layouts with Tailwind CSS Grid

I have a confession: for the first couple of years I wrote CSS professionally, I avoided Grid completely. Flexbox was my hammer, and every layout was a nail. Nested flex containers, negative margin hacks, flex-basis: 33.333% — I made it all work, sort of. Then I opened the TechHub dashboard I was building on an actual tablet and watched my carefully nested flexbox layout fold into something that looked like a ransom note.

That was the project that finally forced me to sit down and learn CSS Grid properly, through Tailwind's utilities. This post is more or less everything I wish someone had told me before I started.

Why Grid, and why through Tailwind

Flexbox is one-dimensional. It's brilliant for a navbar, a button group, or centering an icon next to text. But the moment you're arranging content in two dimensions — rows and columns that need to stay in sync — flexbox makes you fight for it.

Grid thinks in two dimensions natively. And Tailwind's grid utilities map almost one-to-one onto the CSS spec, so nothing you learn here is wasted. grid-cols-3 is just grid-template-columns: repeat(3, minmax(0, 1fr)) under the hood, which means you can always drop back to raw CSS when you outgrow the utilities.

The core utilities you'll actually use

Tailwind ships a lot of grid classes, but in practice I use maybe eight of them daily:

<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
  <article class="rounded-xl border p-6">...</article>
  <article class="rounded-xl border p-6">...</article>
  <article class="rounded-xl border p-6">...</article>
</div>

This is the pattern behind maybe 70% of the layouts on TechHub: one column on mobile, two on tablet, three on desktop, with gap-6 doing the spacing work that used to require margin gymnastics.

The important mental shift: the gap belongs to the container, not the children. Coming from flexbox-with-margins, that took a while to stick. No more last:mr-0 hacks, no more space utilities breaking the moment items wrap.

Mobile first, always

Every Tailwind breakpoint prefix is min-width. grid-cols-1 md:grid-cols-2 means "one column by default, two from 768px up". Early on I thought desktop-first and then tried to patch mobile with overrides, and it was miserable. Write the phone layout as the unprefixed default and add columns as space appears.

The TechHub card grid (and where it broke)

A real example. TechHub has a projects page: a grid of cards, each with a thumbnail, title, tags, and stats. My first version was exactly what you'd expect:

<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">

It looked great on my monitor. Then someone on a 1280px laptop reported that the cards looked "squished" — four columns at lg gave each card barely 280px, and the titles started wrapping in ugly ways. The fix I eventually settled on:

<div class="grid grid-cols-[repeat(auto-fill,minmax(280px,1fr))] gap-4">

auto-fill with minmax tells the browser: fit as many 280px-minimum columns as you can, then stretch them to fill whatever is left. No breakpoints at all. The grid re-flows at whatever width it needs to, not at the widths I guessed in advance.

I don't reach for this on every grid — plain breakpoint columns are easier to read and more predictable — but for card grids where the content defines the minimum size, auto-fill plus minmax has been bulletproof.

One gotcha: learn the difference between auto-fill and auto-fit. auto-fit collapses empty tracks, so with only two cards on a wide screen they stretch enormously. auto-fill keeps the empty tracks, so cards stay a sane size. I got this backwards the first time and spent a while wondering why two lonely cards were each the width of a small country.

Spanning: the dashboard shell

The TechHub admin dashboard is where Grid really paid for itself. The layout brief: a stats row, a big chart, a sidebar of recent activity, and a full-width table. With flexbox this would have been three nested containers. With Grid it's one:

<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
  <!-- stats row: spans full width, runs its own inner grid -->
  <div class="lg:col-span-3 grid grid-cols-2 lg:grid-cols-4 gap-4">
    <StatCard /> <StatCard /> <StatCard /> <StatCard />
  </div>

  <!-- chart takes 2/3, activity feed takes 1/3 -->
  <section class="lg:col-span-2">...chart...</section>
  <aside>...recent activity...</aside>

  <!-- table spans everything -->
  <section class="lg:col-span-3">...table...</section>
</div>

The col-span utilities are the whole trick. On mobile everything stacks in source order; at lg the spans kick in and it becomes the dashboard. One container, no nesting for the main structure, and the source order still makes sense for screen readers.

row-span exists too, and it's handy for things like a tall featured card in a masonry-ish layout — but in practice I use it maybe once per project.

What I'd actually do

Some opinions after a year of shipping Tailwind grid layouts:

Start with one column and add breakpoints only when the design breaks. Not when you think it might break — when it actually does, in the browser, at a real width. Half of my early responsive bugs were breakpoints I added speculatively.

Use auto-fill plus minmax for card grids, breakpoint columns for page shells. Card grids have content-driven sizing; page layouts have design-driven sizing. They're different problems and deserve different tools.

Don't abandon flexbox. Navbars, button rows, vertically centering things — flexbox is still the right call for anything one-dimensional. My rule on TechHub became: Grid for the page, flex for the components inside it. That split has served me well.

Go easy on arbitrary values. The auto-fill pattern above earns its arbitrary value because there's no built-in utility for it. But when I see gap-[13px] or a hand-rolled fraction like 1fr 2.37fr in a code review, it usually means the design hasn't been thought through — and it quietly kills the consistency Tailwind exists to give you.

Resize the browser like you mean it. Not just the three breakpoint widths — drag it slowly across the whole range. The bugs live between the breakpoints. That squished-card bug shipped because I only ever checked 375, 768, and 1440.

Final thoughts

CSS Grid through Tailwind turned out to be one of those skills with a strangely high return on a small time investment. The responsive grid-cols utilities, gap, and col-span cover the overwhelming majority of real layouts, and the auto-fillminmax pattern covers most of what's left.

If you're where I was — flexboxing everything and quietly dreading two-dimensional layouts — pick one page and rebuild it with Grid. It will feel awkward for about an hour, and then you'll wonder why you waited so long. I certainly did.

A

Admin User

TechHub Administrator

Passionate about building great software and sharing knowledge with the developer community.

Comments

Leave a Comment