CSS3 & Responsive Design

Advanced Grid: Auto-Fill, Auto-Fit & Subgrid

30 min Lesson 31 of 60

Introduction to Responsive Grid Without Media Queries

In earlier lessons, you learned how to create grid layouts with explicit column and row definitions. You also learned how media queries let you redefine grid templates at different breakpoints. But CSS Grid has an even more powerful capability: it can automatically create as many columns as will fit in the available space, without a single media query. This is achieved through the repeat() function combined with the auto-fill and auto-fit keywords. These features, combined with minmax(), let you build truly fluid, responsive layouts that adapt seamlessly to any screen size.

In this lesson, we will master auto-fill and auto-fit, understand their critical difference, explore the powerful repeat(auto-fill, minmax()) pattern, then dive into the revolutionary subgrid feature that allows child grids to inherit their parent's track sizing. We will also cover masonry-style layouts, grid combined with aspect-ratio, and advanced responsive patterns that eliminate the need for breakpoints entirely.

The repeat() Function Revisited

Before diving into auto-fill and auto-fit, let us review the repeat() function. You have seen it used with a fixed count: repeat(3, 1fr) creates exactly three equal-width columns. But repeat() is far more versatile than that. Its first argument can be a fixed integer, or it can be one of two special keywords: auto-fill or auto-fit. These keywords tell the browser to calculate the number of tracks dynamically based on the available space.

Example: Fixed repeat vs Auto Keywords

/* Fixed: always exactly 4 columns */
.grid-fixed {
    display: grid;
    grid-template-columns: repeat(4, 250px);
}

/* Dynamic: as many 250px columns as will fit */
.grid-dynamic {
    display: grid;
    grid-template-columns: repeat(auto-fill, 250px);
}

With repeat(4, 250px), the grid always creates four columns of 250px each, regardless of the container width. If the container is narrower than 1000px, the columns will overflow. With repeat(auto-fill, 250px), the browser calculates how many 250px columns can fit within the container width and creates exactly that many. On a 1200px container, you get 4 columns (with 200px of leftover space). On a 600px container, you get 2 columns. The layout adapts automatically.

auto-fill: Creating Tracks to Fill the Container

The auto-fill keyword tells the grid to repeat the track pattern as many times as possible without overflowing the container. If the container is 1000px wide and each track is 200px, the browser creates 5 columns. The key behavior of auto-fill is that it creates tracks even if there are no grid items to place in them. Empty tracks are preserved and take up space in the grid.

Example: auto-fill with Fixed Track Size

.gallery {
    display: grid;
    grid-template-columns: repeat(auto-fill, 200px);
    gap: 16px;
}

/* Container is 900px wide:
   900 / 200 = 4.5 -> 4 columns of 200px
   Remaining 100px is leftover space at the end */

Notice that with fixed-size tracks (like 200px), you often end up with leftover space at the end of the row. The columns stay at exactly 200px, and whatever space does not fit another column just remains empty. This is where minmax() becomes essential.

The Power Pattern: repeat(auto-fill, minmax(min, 1fr))

The most useful and widely-used grid pattern combines auto-fill with minmax(). The minmax() function defines a size range for a track: it will be at least the minimum size, and at most the maximum size. When you use 1fr as the maximum, the tracks will grow equally to fill all available space.

Example: The Golden Pattern

.card-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
    gap: 20px;
}

/* How it works:
   1. Browser calculates how many 250px columns fit
   2. Creates that many columns
   3. Each column then grows equally (via 1fr) to fill remaining space

   Container 1200px: 4 columns of 300px each (250px min, stretched to 300px)
   Container 800px:  3 columns of ~267px each
   Container 550px:  2 columns of 275px each
   Container 300px:  1 column of 300px

   NO media queries needed! */

This is arguably the most important CSS Grid pattern to memorize. It creates a responsive grid that automatically adjusts the number of columns based on available space, while ensuring that items never get smaller than the minimum width and always stretch to fill the container with no leftover space. It works beautifully for card grids, product listings, image galleries, and any repeating content.

Pro Tip: The minimum value in minmax() determines your breakpoints. A minimum of 250px means roughly: 1 column below 500px, 2 columns at 500-749px, 3 columns at 750-999px, and so on. Adjust this minimum value to control when columns wrap. Smaller minimums create more columns sooner; larger minimums create fewer columns.

auto-fit: Collapsing Empty Tracks

The auto-fit keyword works almost identically to auto-fill, with one critical difference: auto-fit collapses empty tracks down to zero width. This means that if there are fewer grid items than the number of tracks that could fit, the empty tracks disappear, and the existing items stretch to fill the entire container width.

Example: auto-fill vs auto-fit with Few Items

/* auto-fill: empty tracks remain, items stay at minimum size */
.auto-fill-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
    gap: 16px;
}

/* auto-fit: empty tracks collapse, items stretch to fill container */
.auto-fit-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
    gap: 16px;
}

The difference only becomes visible when there are fewer items than the number of possible columns. Imagine a 1200px container with the minimum set to 200px. Six columns of 200px could fit. If you have 6 items, both auto-fill and auto-fit behave identically -- all 6 columns are filled, and each stretches to 200px (or more with 1fr). But if you have only 3 items:

  • auto-fill: Creates 6 columns (because 6 can fit). Three are filled with items, three remain empty but still take up space. Each column is 200px, and there is no stretching because the empty columns absorb the space.
  • auto-fit: Creates 6 columns initially, but then collapses the 3 empty ones to 0px. The 3 filled columns then stretch equally (via 1fr) to fill the full 1200px, making each one 400px wide.

Visual Comparison: 3 Items in a 1200px Container

/* auto-fill with minmax(200px, 1fr): */
/* |--200px--|--200px--|--200px--|--200px--|--200px--|--200px--| */
/* | Item 1  | Item 2  | Item 3  | (empty) | (empty) | (empty) | */

/* auto-fit with minmax(200px, 1fr): */
/* |------400px------|------400px------|------400px------| */
/* |     Item 1      |     Item 2      |     Item 3      | */
Note: When the grid is full (all possible tracks have items), auto-fill and auto-fit produce identical results. The difference only matters when there are fewer items than available tracks. Choose auto-fit when you want items to stretch and fill the row, and auto-fill when you want items to maintain consistent sizing regardless of count.

When to Use auto-fill vs auto-fit

Choosing between the two depends on your design intent:

  • Use auto-fill when: You want consistent item sizes regardless of how many items there are. This is ideal for design systems where cards must always be the same width, or when you are building a grid where items might be dynamically added or removed and you want the layout to feel "slotted."
  • Use auto-fit when: You want items to expand and take up all available space. This is ideal for hero sections with a variable number of feature cards, navigation items that should spread across the header, or any context where you want the content to feel full and generous.

Example: Navigation Bar with auto-fit

.nav-bar {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
    gap: 8px;
    padding: 12px;
    background: #1a1a2e;
}

.nav-item {
    text-align: center;
    padding: 10px 16px;
    color: white;
    background: rgba(255, 255, 255, 0.1);
    border-radius: 6px;
}

/* Whether you have 4 or 8 nav items, they will always
   stretch to fill the full width of the navigation bar */

Example: Product Grid with auto-fill

.product-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
    gap: 24px;
    padding: 24px;
}

.product-card {
    background: white;
    border-radius: 12px;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
    overflow: hidden;
}

/* Products always maintain consistent card sizes.
   If a row has room for 4 cards but only 2 exist,
   the remaining space stays empty (not stretched). */

Handling Edge Cases with auto-fill and minmax

There are some important edge cases to be aware of when using the auto-fill/minmax pattern:

Edge Case: Container Narrower Than Minimum

/* Problem: if the container is narrower than 250px,
   the item overflows because it cannot shrink below 250px */
.grid {
    grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
}

/* Solution: use min() to set a responsive minimum */
.grid {
    grid-template-columns: repeat(auto-fill, minmax(min(250px, 100%), 1fr));
}

/* Now the minimum is either 250px or 100% of the container,
   whichever is smaller. On a 200px container, the column
   becomes 200px (100%) instead of overflowing at 250px. */
Warning: The repeat(auto-fill, minmax(250px, 1fr)) pattern can cause horizontal overflow on very narrow containers (like mobile screens with padding). Always consider using min(250px, 100%) as your minimum to prevent this. The pattern repeat(auto-fill, minmax(min(250px, 100%), 1fr)) is the truly bulletproof version.

Introduction to Subgrid

Subgrid is one of the most requested and powerful additions to CSS Grid. Before subgrid, nested grids were completely independent -- a child grid had no awareness of its parent grid's tracks. This made it impossible to align content across sibling grid items without hacky workarounds. Subgrid solves this by allowing a child grid to adopt the track definitions of its parent grid, creating perfect alignment between sibling items.

The subgrid keyword is used as a value for grid-template-columns and/or grid-template-rows on a grid item that is itself a grid container. When you set grid-template-columns: subgrid, the child grid uses the parent's column tracks for the portion of the parent grid it occupies, rather than defining its own.

Example: Basic Subgrid Syntax

.parent-grid {
    display: grid;
    grid-template-columns: 1fr 2fr 1fr;
    gap: 20px;
}

.child-item {
    grid-column: 1 / 4;  /* Spans all 3 parent columns */

    display: grid;
    grid-template-columns: subgrid;  /* Inherits parent's 3 columns */
    /* The child now has 3 columns matching the parent's 1fr 2fr 1fr */
}

.child-item > .a { grid-column: 1; }  /* Aligns with parent column 1 */
.child-item > .b { grid-column: 2; }  /* Aligns with parent column 2 */
.child-item > .c { grid-column: 3; }  /* Aligns with parent column 3 */
Note: Subgrid is a value for grid-template-columns or grid-template-rows, not a new display value. The child element must first be a grid item (placed on the parent grid) AND also be a grid container (display: grid). It bridges these two roles by inheriting tracks from its parent.

Subgrid for Aligned Card Content

The classic use case for subgrid is aligning content within cards that are laid out in a grid. Without subgrid, if one card has a longer title than another, the titles, descriptions, and buttons across cards will not align horizontally. With subgrid, each card can inherit the parent grid's row tracks, forcing all card sections to align perfectly.

Example: Aligned Cards with Subgrid

<div class="card-grid">
    <article class="card">
        <img src="photo1.jpg" alt="Photo 1">
        <h3>Short Title</h3>
        <p>Brief description of this card.</p>
        <a href="#" class="btn">Read More</a>
    </article>
    <article class="card">
        <img src="photo2.jpg" alt="Photo 2">
        <h3>A Much Longer Card Title That Wraps to Two Lines</h3>
        <p>A longer description that has more text and takes up more space.</p>
        <a href="#" class="btn">Read More</a>
    </article>
    <article class="card">
        <img src="photo3.jpg" alt="Photo 3">
        <h3>Medium Title Here</h3>
        <p>Medium description.</p>
        <a href="#" class="btn">Read More</a>
    </article>
</div>

CSS: Subgrid for Card Alignment

.card-grid {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    /* Define 4 row tracks for each card's content sections */
    grid-template-rows: auto auto auto auto;
    gap: 24px;
}

.card {
    grid-row: span 4;           /* Each card spans 4 parent rows */
    display: grid;
    grid-template-rows: subgrid; /* Inherit parent's row tracks */
    background: white;
    border-radius: 12px;
    overflow: hidden;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}

.card img {
    width: 100%;
    height: 200px;
    object-fit: cover;
}

.card h3 {
    padding: 16px 16px 8px;
    margin: 0;
}

.card p {
    padding: 0 16px;
    color: #666;
    margin: 0;
}

.card .btn {
    align-self: end;
    margin: 16px;
    padding: 10px 20px;
    background: #667eea;
    color: white;
    text-decoration: none;
    border-radius: 6px;
    text-align: center;
}

/* Result: All card images align, all titles align,
   all descriptions align, and all buttons align
   horizontally across the row -- regardless of
   content length differences! */

This is a game-changer for card-based layouts. Before subgrid, achieving this alignment required either fixed heights (which breaks with dynamic content), JavaScript (which is fragile and slow), or complex Flexbox hacks. Subgrid solves it elegantly with pure CSS.

Subgrid for Both Axes

You can apply subgrid to both columns and rows simultaneously. This is useful when a child element spans multiple columns and multiple rows of the parent grid and needs to align its internal content with both the parent's column and row tracks.

Example: Two-Axis Subgrid

.parent {
    display: grid;
    grid-template-columns: 200px 1fr 1fr 200px;
    grid-template-rows: auto 1fr auto;
    gap: 16px;
    min-height: 100vh;
}

.featured-section {
    grid-column: 2 / 4;  /* Spans 2 middle columns */
    grid-row: 1 / 4;     /* Spans all 3 rows */

    display: grid;
    grid-template-columns: subgrid;  /* Inherits 2 parent columns */
    grid-template-rows: subgrid;     /* Inherits 3 parent rows */
    gap: 16px;
}

/* Children of .featured-section now align perfectly
   with the parent grid's column and row tracks */
.featured-section .headline {
    grid-column: 1 / 3;  /* Spans both inherited columns */
    grid-row: 1;
}

.featured-section .article {
    grid-column: 1;
    grid-row: 2;
}

.featured-section .sidebar-content {
    grid-column: 2;
    grid-row: 2;
}

Subgrid and Gap Inheritance

When a child uses subgrid, it inherits the parent's tracks but NOT automatically the parent's gap. The child grid uses its own gap property. However, the subgrid tracks are calculated including the parent's gaps, so the alignment is maintained. You can set a different gap on the subgrid or even set it to zero.

Example: Subgrid with Custom Gap

.parent {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: 24px;  /* Parent gap */
}

.child {
    grid-column: 1 / 5;
    display: grid;
    grid-template-columns: subgrid;
    gap: 8px;   /* Child can have its own gap */
    /* Column tracks still align with parent, but gutters
       between child items are 8px instead of 24px */
}

/* To match parent gap exactly: */
.child-matching {
    grid-column: 1 / 5;
    display: grid;
    grid-template-columns: subgrid;
    /* gap is inherited from parent by default in subgrid */
}

Practical Subgrid: Form Layout

Forms are another excellent use case for subgrid. Labels and inputs across multiple form rows should align perfectly, but label widths vary based on text length. Subgrid makes this alignment automatic.

Example: Aligned Form with Subgrid

<form class="form-grid">
    <div class="form-row">
        <label for="name">Full Name</label>
        <input type="text" id="name">
        <span class="hint">Required</span>
    </div>
    <div class="form-row">
        <label for="email">Email Address</label>
        <input type="email" id="email">
        <span class="hint">We will never share your email</span>
    </div>
    <div class="form-row">
        <label for="phone">Phone</label>
        <input type="tel" id="phone">
        <span class="hint">Optional</span>
    </div>
</form>

CSS: Form Subgrid

.form-grid {
    display: grid;
    grid-template-columns: auto 1fr auto;
    gap: 16px 24px;
    max-width: 600px;
}

.form-row {
    grid-column: 1 / -1;       /* Span all 3 columns */
    display: grid;
    grid-template-columns: subgrid;  /* Inherit parent columns */
    align-items: center;
}

.form-row label {
    font-weight: 600;
    white-space: nowrap;
}

.form-row input {
    padding: 8px 12px;
    border: 1px solid #ddd;
    border-radius: 6px;
}

.form-row .hint {
    font-size: 0.85rem;
    color: #888;
}

/* All labels align in the first column (auto-sized to the widest).
   All inputs align in the second column (flexible).
   All hints align in the third column. */

Masonry Layout Patterns

A masonry layout (like Pinterest) has items of varying heights that pack tightly without gaps, filling in vertically where space is available. While CSS does not have a native masonry value in the stable Grid specification yet, there is an experimental grid-template-rows: masonry value being developed. In the meantime, you can approximate masonry effects using Grid and some creative techniques.

Example: Approximate Masonry with Grid

.masonry-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
    grid-auto-rows: 10px;  /* Small row height for fine-grained control */
    gap: 16px;
}

/* Each item spans a different number of rows based on content height */
.masonry-item.short  { grid-row: span 20; }  /* ~200px */
.masonry-item.medium { grid-row: span 30; }  /* ~300px */
.masonry-item.tall   { grid-row: span 45; }  /* ~450px */

.masonry-item {
    background: white;
    border-radius: 12px;
    overflow: hidden;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}

.masonry-item img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}
Note: True CSS masonry layout (using grid-template-rows: masonry) is currently being implemented by browser vendors. Firefox has experimental support behind a flag. When it becomes widely supported, you will be able to achieve perfect masonry layouts with a single CSS property. Until then, the grid-auto-rows spanning technique shown above is the best pure-CSS approximation. For production masonry layouts, JavaScript libraries like Masonry.js remain the most reliable solution.

Example: Future Native Masonry Syntax

/* Experimental -- not yet widely supported */
.masonry-native {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
    grid-template-rows: masonry;  /* Items pack vertically! */
    gap: 16px;
}

/* With this, items automatically fill vertical gaps
   without needing to specify row spans manually. */

Complex Responsive Patterns Without Media Queries

By combining auto-fill, minmax(), clamp(), and CSS custom properties, you can create sophisticated responsive patterns that require zero media queries. These patterns are more fluid than breakpoint-based designs because they adapt continuously rather than jumping between fixed states.

Example: Fully Fluid Card Grid with Custom Properties

:root {
    --card-min: 280px;
    --card-gap: 24px;
}

.fluid-grid {
    display: grid;
    grid-template-columns: repeat(
        auto-fill,
        minmax(min(var(--card-min), 100%), 1fr)
    );
    gap: var(--card-gap);
    padding: var(--card-gap);
}

/* Change the minimum card width for different sections */
.fluid-grid.narrow-cards {
    --card-min: 200px;
}

.fluid-grid.wide-cards {
    --card-min: 400px;
}

Example: Responsive Sidebar Layout Without Media Queries

.sidebar-layout {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(300px, 100%), 1fr));
    gap: 32px;
}

.sidebar-layout .main-content {
    min-width: 0;  /* Prevent overflow from wide content */
}

.sidebar-layout .sidebar {
    min-width: 0;
}

/* When the container is wide enough, this creates 2 or more columns.
   When it is narrow, everything stacks into a single column.
   The sidebar and main content sizes are determined by the
   auto-fit + minmax combination automatically. */

Example: RAM (Repeat, Auto, Minmax) Pattern Variations

/* Small items: many columns on wide screens */
.icons-grid {
    grid-template-columns: repeat(auto-fill, minmax(80px, 1fr));
}

/* Medium items: standard card grid */
.card-grid {
    grid-template-columns: repeat(auto-fill, minmax(min(300px, 100%), 1fr));
}

/* Large items: few columns, items take more space */
.feature-grid {
    grid-template-columns: repeat(auto-fill, minmax(min(500px, 100%), 1fr));
}

/* Asymmetric: different minimum for first column */
.asymmetric {
    grid-template-columns: minmax(200px, 1fr) repeat(auto-fill, minmax(300px, 2fr));
}

Grid Combined with aspect-ratio

The aspect-ratio CSS property pairs beautifully with Grid layouts, especially for image galleries and media grids. When you set an aspect ratio on grid items, they maintain their proportions as the grid columns resize, creating visually consistent layouts without specifying fixed heights.

Example: Image Gallery with Aspect Ratio

.image-gallery {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
    gap: 12px;
}

.gallery-item {
    aspect-ratio: 1 / 1;  /* Perfect squares */
    overflow: hidden;
    border-radius: 8px;
}

.gallery-item img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    transition: transform 0.3s ease;
}

.gallery-item:hover img {
    transform: scale(1.05);
}

Example: Video Grid with 16:9 Aspect Ratio

.video-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(min(320px, 100%), 1fr));
    gap: 20px;
}

.video-thumbnail {
    aspect-ratio: 16 / 9;
    background: #000;
    border-radius: 12px;
    overflow: hidden;
    position: relative;
}

.video-thumbnail img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.play-button {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    width: 60px;
    height: 60px;
    background: rgba(255, 255, 255, 0.9);
    border-radius: 50%;
}

Example: Mixed Aspect Ratios in a Grid

.media-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
    gap: 16px;
}

.media-item.landscape { aspect-ratio: 16 / 9; }
.media-item.portrait  { aspect-ratio: 3 / 4;
                        grid-row: span 2; }
.media-item.square    { aspect-ratio: 1 / 1; }

.media-item {
    overflow: hidden;
    border-radius: 8px;
    background: #f0f0f0;
}

.media-item img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

Performance Considerations

Understanding the performance implications of advanced grid features helps you make informed decisions:

  • auto-fill and auto-fit: These have negligible performance impact. The browser's grid algorithm handles track calculation efficiently, even with many items. However, hundreds of grid items on a single page can still cause layout performance issues -- consider virtual scrolling for very long lists.
  • Subgrid: Subgrid adds a dependency between parent and child layout calculations. The parent must calculate its tracks before the child can use them. For typical page layouts (2-3 nesting levels), this is imperceptible. Deeply nested subgrids (5+ levels) could theoretically impact performance, though this is an unusual pattern.
  • minmax() with complex calculations: Using minmax() with calc(), min(), max(), or clamp() does not significantly impact performance. These calculations are resolved once during the layout phase.
  • Resize performance: auto-fill/auto-fit grids recalculate the number of columns on every container resize. The browser is optimized for this, but combining it with heavy content (large images, complex nested layouts) can feel sluggish on low-end devices. Use content-visibility: auto for off-screen items to improve scroll performance.

Practical Example: Complete Responsive Portfolio

Let us bring together everything from this lesson into a complete, responsive portfolio page that uses no media queries for its grid layouts.

HTML: Portfolio Structure

<div class="portfolio">
    <section class="hero-section">
        <h1>My Portfolio</h1>
        <p>Web Developer & Designer</p>
    </section>

    <section class="skills-section">
        <div class="skill-card">HTML</div>
        <div class="skill-card">CSS</div>
        <div class="skill-card">JavaScript</div>
        <div class="skill-card">React</div>
        <div class="skill-card">Node.js</div>
        <div class="skill-card">Python</div>
    </section>

    <section class="projects-section">
        <article class="project-card">
            <img src="project1.jpg" alt="Project 1">
            <h3>E-Commerce Platform</h3>
            <p>Full-stack online store</p>
            <a href="#">View Project</a>
        </article>
        <!-- More project cards... -->
    </section>

    <section class="contact-section">
        <form class="contact-form">
            <div class="form-row">
                <label>Name</label>
                <input type="text">
            </div>
            <div class="form-row">
                <label>Email</label>
                <input type="email">
            </div>
            <div class="form-row">
                <label>Message</label>
                <textarea></textarea>
            </div>
        </form>
    </section>
</div>

CSS: Complete Responsive Portfolio

/* Skills: auto-fit to fill available space */
.skills-section {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(150px, 100%), 1fr));
    gap: 16px;
    padding: 40px 24px;
}

.skill-card {
    background: linear-gradient(135deg, #667eea, #764ba2);
    color: white;
    padding: 24px;
    border-radius: 12px;
    text-align: center;
    font-weight: 700;
    font-size: 1.1rem;
}

/* Projects: auto-fill for consistent card sizes */
.projects-section {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(min(300px, 100%), 1fr));
    /* Subgrid rows for aligned card content */
    grid-template-rows: repeat(auto-fill, auto auto auto auto);
    gap: 24px;
    padding: 40px 24px;
}

.project-card {
    grid-row: span 4;
    display: grid;
    grid-template-rows: subgrid;
    background: white;
    border-radius: 12px;
    overflow: hidden;
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
}

.project-card img {
    aspect-ratio: 16 / 9;
    width: 100%;
    object-fit: cover;
}

.project-card h3 {
    padding: 16px 20px 0;
    margin: 0;
}

.project-card p {
    padding: 8px 20px;
    color: #666;
    margin: 0;
}

.project-card a {
    align-self: end;
    margin: 0 20px 20px;
    padding: 10px;
    background: #667eea;
    color: white;
    text-align: center;
    border-radius: 8px;
    text-decoration: none;
}

/* Contact form: subgrid for label alignment */
.contact-form {
    display: grid;
    grid-template-columns: auto 1fr;
    gap: 16px 24px;
    max-width: 500px;
    margin: 0 auto;
    padding: 40px 24px;
}

.form-row {
    grid-column: 1 / -1;
    display: grid;
    grid-template-columns: subgrid;
    align-items: start;
}

/* Zero media queries -- fully responsive! */

Browser Support and Progressive Enhancement

Before using these features in production, understand their browser support:

  • auto-fill and auto-fit: Supported in all modern browsers since 2017. Safe to use in production without fallbacks.
  • Subgrid: Supported in Firefox (since version 71), Chrome (since version 117), Safari (since version 16), and Edge (since version 117). For older browsers, provide a non-subgrid fallback using @supports.
  • Masonry layout: Only experimentally supported in Firefox behind a flag. Not ready for production. Use JavaScript solutions for cross-browser masonry.
  • aspect-ratio: Supported in all modern browsers since 2021. Safe to use with a padding-based fallback for very old browsers.

Example: Progressive Enhancement for Subgrid

/* Base layout without subgrid */
.card {
    display: grid;
    grid-template-rows: auto auto 1fr auto;
}

/* Enhanced layout with subgrid when supported */
@supports (grid-template-rows: subgrid) {
    .card-grid {
        grid-template-rows: repeat(auto-fill, auto auto auto auto);
    }

    .card {
        grid-row: span 4;
        grid-template-rows: subgrid;
    }
}
Pro Tip: Use the @supports rule to progressively enhance your layouts with subgrid. The base layout should work well without subgrid (using regular grid rows), and then subgrid adds the alignment refinement for browsers that support it. This ensures a good experience for all users while leveraging the best features for modern browsers.

Practice Exercise

Exercise 1: Build a responsive image gallery using repeat(auto-fill, minmax(min(200px, 100%), 1fr)). Add 12 images with aspect-ratio: 1/1 for squares. Test it by resizing your browser window from very narrow (300px) to very wide (1400px) and observe how columns are added and removed automatically without any media queries.

Exercise 2: Create a comparison between auto-fill and auto-fit. Build two identical grids side by side, each with only 3 card items, in a wide container (1200px). Use auto-fill for the first grid and auto-fit for the second. Set the minimum card width to 200px. Observe and document the visual difference between how the two grids handle the extra space.

Exercise 3: Build a card grid with 6 cards using subgrid to align card content. Each card should have an image, a title, a description (of varying lengths), and a button. Use grid-template-rows: subgrid on the cards so that titles, descriptions, and buttons align horizontally across all cards in the same row. Use @supports to provide a fallback for browsers without subgrid support.

Exercise 4: Create an entire responsive page layout that uses zero media queries. Use auto-fill or auto-fit with minmax() for all grid sections. The page should include a hero section, a skills/icons grid, a projects grid, a testimonials section, and a footer. Every section should adapt fluidly from 320px to 1920px container widths.