Skip to content

Fix: CSS Grid Layout Not Working — Items Not Placing or Spanning Correctly

FixDevs · (Updated: )

Part of:  React & Frontend Errors

Quick Answer

How to fix CSS Grid issues — implicit vs explicit grid, grid-template-areas, auto-placement, subgrid, alignment, and common mistakes with grid-column and grid-row.

The Problem

CSS Grid items don’t position where expected:

.container {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
}

.item {
  grid-column: 2 / 4; /* Supposed to span columns 2 and 3 */
}
/* Item doesn't span correctly — or disappears entirely */

Or a named area layout doesn’t render:

.container {
  display: grid;
  grid-template-areas:
    "header header"
    "sidebar main"
    "footer footer";
}

.header { grid-area: header; } /* Has no effect */

Or items overflow the grid or stack instead of spreading across columns.

Why This Happens

CSS Grid has a large surface area and several non-obvious behaviors:

  • display: grid on the parent, not the children — a common mistake is applying display: grid to the items rather than the container.
  • Implicit vs explicit grid — columns/rows you define are “explicit.” Items that don’t fit create “implicit” tracks with auto sizing, which may not match your intent.
  • Line numbering starts at 1grid-column: 1 / 3 spans from line 1 to line 3 (covering 2 columns). Negative numbers count from the end: -1 is the last line.
  • grid-template-areas requires every cell — each row in the template must have the same number of cells, and every area name must form a rectangle. A . represents an empty cell.
  • grid-area name must match exactlygrid-area: header requires "header" in grid-template-areas. Case-sensitive.
  • gap vs grid-gapgrid-gap is deprecated. Use gap. Some older browsers need the prefix.

The most expensive failure mode is the production-only regression — your grid layout looks correct on your laptop but breaks on iOS Safari, on a 4K monitor, or on a 320px-wide Android device. These breakages are invisible during local development because you only test one or two viewports. Real users hit them every day, and unless you have visual regression testing in your pipeline, you do not see the problem until a customer screenshots it.

The blast radius is per-browser, per-viewport segment of your user base. Grid renders subtly differently in Safari (subgrid was late, container queries had issues, and certain minmax interactions still misbehave on older iOS versions). Firefox renders subtly differently from Chrome on auto-fit empty track collapse. A Galaxy Fold with its 280px portrait width will overflow grids that look fine at 320px. The user impact is “the site looks broken” — content stacked on top of itself, horizontal scroll, hero images that consume the full viewport. This is the kind of visual regression that tanks conversion rates and looks unprofessional without producing any log error or analytics event.

Fix 1: Verify display: grid Is on the Container

display: grid turns an element into a grid container. Its direct children become grid items:

/* WRONG — grid on the item, not the container */
.item {
  display: grid;
  grid-column: 1 / 3;
}

/* CORRECT — grid on the container */
.container {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 1rem;
}

.item {
  grid-column: 1 / 3; /* Now this works */
}

Grid only affects direct children:

<div class="container">      <!-- display: grid here -->
  <div class="item">         <!-- Direct child = grid item -->
    <span>Not a grid item</span>  <!-- Grandchild = NOT a grid item -->
  </div>
</div>

Fix 2: Understand Grid Line Numbers

Grid lines are numbered starting at 1. A 3-column grid has 4 vertical lines:

  1     2     3     4
  |  A  |  B  |  C  |

grid-column: 1 / 2  → column A only
grid-column: 1 / 3  → columns A and B
grid-column: 2 / 4  → columns B and C
grid-column: 1 / -1 → all columns (1 to last line)
grid-column: span 2 → span 2 columns from current position
.container {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
}

/* Span from column 2 to end */
.item {
  grid-column: 2 / -1;   /* Line 2 to last line */
}

/* Span 2 columns starting from wherever auto-placed */
.wide-item {
  grid-column: span 2;
}

/* Place at specific position */
.positioned {
  grid-column: 2;        /* Shorthand for 2 / 3 */
  grid-row: 1 / 3;       /* Span 2 rows */
}

Fix 3: Fix grid-template-areas

Named areas must form complete rectangles, and every row needs the same cell count:

/* WRONG — uneven row lengths */
.container {
  display: grid;
  grid-template-areas:
    "header header header"
    "sidebar main"      /* Only 2 cells — error */
    "footer";           /* Only 1 cell — error */
}

/* WRONG — area doesn't form a rectangle */
.container {
  display: grid;
  grid-template-areas:
    "header sidebar"
    "main   sidebar"
    "footer header"; /* 'header' appears in two non-contiguous rows — invalid */
}

/* CORRECT — rectangular areas, equal row lengths */
.container {
  display: grid;
  grid-template-columns: 200px 1fr;
  grid-template-rows: auto 1fr auto;
  grid-template-areas:
    "header  header"
    "sidebar main  "
    "footer  footer";
  min-height: 100vh;
}

.header  { grid-area: header; }
.sidebar { grid-area: sidebar; }
.main    { grid-area: main; }
.footer  { grid-area: footer; }

Use . for empty cells:

.container {
  grid-template-areas:
    "logo    nav    nav"
    ".       content content"
    "footer  footer  footer";
}
/* The dot '.' leaves that cell empty */

Fix 4: Fix Implicit Grid Behavior

When items don’t fit in the explicit grid, they’re placed in the implicit grid using auto-sizing:

.container {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  /* Only defines columns — rows are implicit */
}

/* Implicit rows default to auto (content height) */
/* To control implicit row size: */
.container {
  grid-auto-rows: 200px;          /* Fixed height */
  grid-auto-rows: minmax(100px, auto); /* Min 100px, grows with content */
}

/* Auto-placement direction */
.container {
  grid-auto-flow: row;    /* Default: fill rows left to right */
  grid-auto-flow: column; /* Fill columns top to bottom */
  grid-auto-flow: row dense; /* Fill gaps when items span multiple cells */
}

dense packing to fill gaps:

/* Without dense: a 2-column-wide item leaves gaps before smaller items */
/* With dense: smaller items backfill gaps */
.container {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-auto-flow: row dense;
}

.wide { grid-column: span 2; }
.tall { grid-row: span 2; }

Fix 5: Alignment and Stretching

Grid items stretch to fill their cell by default. Override with alignment properties:

/* Container-level alignment (affects ALL items) */
.container {
  display: grid;
  grid-template-columns: repeat(3, 1fr);

  /* Align items along the row axis (horizontal) */
  justify-items: stretch; /* default */
  justify-items: start;
  justify-items: end;
  justify-items: center;

  /* Align items along the column axis (vertical) */
  align-items: stretch;   /* default */
  align-items: start;
  align-items: end;
  align-items: center;

  /* Shorthand */
  place-items: center;    /* align-items + justify-items */
  place-items: start end; /* vertical horizontal */
}

/* Align the grid tracks within the container */
.container {
  width: 800px;
  grid-template-columns: repeat(3, 200px); /* Total: 600px < 800px */

  justify-content: start;    /* default — tracks at start */
  justify-content: center;   /* tracks centered */
  justify-content: space-between;
  justify-content: space-around;
}

/* Per-item alignment override */
.item {
  justify-self: center;  /* Override justify-items for this item */
  align-self: end;       /* Override align-items for this item */
}

Fix 6: Use minmax() and fr Units Correctly

fr units and minmax() enable flexible layouts without overflow:

/* fr unit — fraction of remaining space */
.container {
  display: grid;

  /* 3 equal columns */
  grid-template-columns: repeat(3, 1fr);

  /* 1fr min 200px — never smaller than 200px */
  grid-template-columns: repeat(3, minmax(200px, 1fr));

  /* Auto-fill: as many columns as fit, min 200px each */
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));

  /* Auto-fit: same as auto-fill but collapses empty tracks */
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
}

auto-fill vs auto-fit:

/* auto-fill — creates empty columns to fill the row */
/* auto-fit — collapses empty columns, stretches existing items */

/* For responsive card grids, auto-fit is usually better: */
.card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
  gap: 1.5rem;
}
/* Cards fill the available width at any viewport size */

Avoid fr with content overflow:

/* PROBLEM: fr doesn't prevent overflow from long content */
.container {
  grid-template-columns: 1fr 1fr;
}
.item {
  /* Long word or code block can overflow a 1fr column */
}

/* FIX: use minmax with min-content */
.container {
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  /* minmax(0, 1fr) allows column to shrink below content size */
}

Fix 7: Subgrid for Nested Alignment

Subgrid lets nested grids align to the parent grid’s tracks:

/* Parent grid */
.layout {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 1rem;
}

/* Card that spans 2 columns and uses parent's column tracks */
.card {
  grid-column: span 2;
  display: grid;
  grid-template-columns: subgrid; /* Uses parent's 2 remaining column tracks */
}

/* Card content aligns to parent grid lines */
.card-image {
  grid-column: 1;    /* First track of parent grid under the card */
}
.card-text {
  grid-column: 2;    /* Second track */
}

Note: subgrid is supported in all modern browsers as of 2023. Check if your target browsers support it before using.

Fix 8: Test Across Browsers and Viewports Before Deploy

Grid bugs that look fine locally often fail in production for specific browser-viewport combinations. The mitigations are visual regression testing and explicit browser support documentation.

Visual regression test in CI — Playwright, Chromatic, or Percy snapshot the layout at multiple viewport widths and compare against baseline. A 1-pixel difference fails the build. Run on every PR.

// playwright snapshot test
import { test, expect } from '@playwright/test';

const viewports = [
  { name: 'mobile-portrait', width: 320, height: 568 },
  { name: 'mobile-landscape', width: 568, height: 320 },
  { name: 'tablet', width: 768, height: 1024 },
  { name: 'desktop', width: 1280, height: 800 },
  { name: 'wide', width: 1920, height: 1080 },
];

for (const viewport of viewports) {
  test(`grid layout - ${viewport.name}`, async ({ page }) => {
    await page.setViewportSize({ width: viewport.width, height: viewport.height });
    await page.goto('/dashboard');
    await expect(page).toHaveScreenshot(`dashboard-${viewport.name}.png`);
  });
}

Test the matrix that actually matters. Pull your top browsers and viewports from analytics, then test those combinations explicitly. Common gotchas:

  • iOS Safari: min-content track sizing behaves differently than Chrome until iOS 16.4. gap on flex containers was buggy through iOS 14.
  • Samsung Internet on Galaxy Fold: 280px-wide portrait viewport reveals overflow that 320px hides.
  • Old Edge (pre-Chromium): still occasionally shows up in enterprise traffic; supports -ms-grid- prefixes only.
  • Firefox on Windows with text scaling at 125%: causes minmax(200px, 1fr) cells to overflow if you assumed 100% scaling.

Pro Tip: Use the Firefox DevTools Grid inspector. It is materially better than Chrome’s: it draws the grid lines and tracks visually over your layout, including implicit tracks, and labels every line number. Diagnosing a misaligned grid takes 30 seconds in Firefox versus 10 minutes of guessing in Chrome.

Production Incident Playbook: Layout Broken for a Browser Segment

Scenario: A customer in your support inbox includes a screenshot. The dashboard hero is overlapping the navigation, the sidebar is missing, and content is stacked vertically instead of horizontally. They are on iOS 15 Safari. You cannot reproduce the issue on your Mac in Chrome.

Blast radius: Every user on the affected browser-viewport combination. Without analytics segmentation, you have no idea what percentage of traffic this is. iOS Safari is often 20-40% of mobile traffic — losing those users to a broken layout is a major revenue hit.

Detection: Visual regression tests fail in CI (if you have them). Without them, the first signal is a support ticket, which is days late and represents only the most motivated affected users. Most affected users just bounce.

Diagnosis checklist:

  1. Open the URL on a real iOS device (or BrowserStack/Sauce Labs for the exact OS version). Reproduce the bug.
  2. Use Safari’s Web Inspector connected to the device. Inspect the broken grid and check the computed styles.
  3. Compare the computed grid-template-columns and grid-template-rows values between the working browser and the broken one. Differences point at unsupported syntax.
  4. Check caniuse.com for any grid feature you use (subgrid, auto-fit with minmax, named lines) against the failing browser version.
  5. Reproduce the failure in a minimal codepen to isolate the property combination causing the issue.

Recovery: Once you identify the unsupported feature, add a @supports query fallback or a simpler grid pattern that works everywhere. Deploy and confirm via real-device testing. Recovery time depends on the complexity of the fallback — typically 1-4 hours.

/* Fallback for browsers without subgrid */
.card {
  display: grid;
  grid-template-columns: 1fr 1fr; /* Explicit fallback */
}

@supports (grid-template-columns: subgrid) {
  .card {
    grid-template-columns: subgrid;
  }
}

Prevention: Visual regression tests at the viewports your users actually use. A pre-deploy step that runs Playwright against your top 5 browser-viewport combinations catches 90% of grid regressions. See Fix: CSS Flexbox Not Working for parallel testing patterns on the flexbox side.

Still Not Working?

height: 100% on grid items — grid items don’t automatically fill the container’s height unless align-items: stretch (the default) is set on the container. If items have explicit heights set, they won’t stretch.

Overflow from non-grid children — if a non-grid element (e.g., an absolutely positioned child) causes overflow, it won’t affect grid layout but may visually overflow the container. Use overflow: hidden on the container or position: relative on a wrapper.

CSS Grid in Firefox vs Chrome — minor rendering differences exist between browsers. Firefox DevTools has the best CSS Grid inspector — use it to visualize grid lines, areas, and gaps.

grid shorthand is complex — the grid shorthand property combines grid-template-rows, grid-template-columns, grid-template-areas, grid-auto-rows, grid-auto-columns, and grid-auto-flow. Avoid the shorthand until you’re comfortable with each individual property.

Grid items get clipped by a parent’s overflow: hidden — if an ancestor has overflow: hidden, grid items that visually extend beyond the container (negative margins, transforms) are clipped. Audit ancestor overflow properties when layout breaks unexpectedly.

z-index doesn’t work as expected on grid items — grid items participate in stacking contexts when z-index is set. If a grid item with z-index: 10 is hidden under a sibling, check whether an ancestor has created a stacking context that traps the z-index. See Fix: CSS z-index Not Working for the broader pattern.

Print stylesheets break grid layouts — many browsers do not honor grid layout in print mode. Use a @media print block with explicit display: block or a flat layout for printing.

Right-to-left (RTL) languages flip grid placementgrid-column: 1 / 3 starts from the right in RTL mode. Use grid-column: span 2 for direction-independent spans, or set direction: ltr on the container if the design must always read left-to-right.

For related CSS issues, see Fix: CSS Flexbox Not Working, Fix: CSS Animation Not Working, and Fix: CSS position: sticky Not Working.

F

FixDevs

Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.

Was this article helpful?

Related Articles