AI Computer Institute
Expert-curated CS & AI curriculum aligned to CBSE standards. A bharath.ai initiative. About Us

CSS Grid: Creating Complex Layouts

📚 Advanced CSS⏱️ 20 min read🎓 Grade 9
✍️ AI Computer Institute Editorial Team Updated: August 2026 CBSE-aligned · Peer-reviewed · 20 min read
Content curated by subject matter experts with IIT/NIT backgrounds. All chapters are fact-checked against official CBSE/NCERT syllabi.

When One Direction Isn't Enough

Open the CBSE datesheet PDF that your school circulates before board exams. It is a table: subjects run down the left as rows, and dates run across the top as columns. Every exam sits at the intersection of exactly one row and one column. Now try to imagine building that page using only Flexbox, the layout tool you likely met just before this chapter. Flexbox arranges items along a single axis — either a row or a column, one direction at a time. You can make one row of dates line up neatly, or one column of subjects line up neatly, but the moment you need the *rows* to line up with each other AND the *columns* to line up with each other at the same time, Flexbox runs out of ideas. You'd end up nesting flex containers inside flex containers, manually matching widths by eye, and the layout would break the instant one subject name wrapped onto two lines.

This is exactly the problem CSS Grid was built to solve. Where Flexbox thinks in one dimension (a line of items), Grid thinks in two dimensions simultaneously (rows and columns together, like the datesheet, like an IRCTC train seat chart with berths across and coaches down, like a cricket points table with teams down and Played/Won/Lost/Points across). Grid lets you declare the entire two-dimensional skeleton of a layout in one place, and then every child element simply reports which row and column it belongs in — the browser does the alignment math for you, in both directions, automatically and permanently correct even as content changes size.

The Grid Vocabulary: Lines, Tracks, Cells, and Gaps

Before writing any CSS, you need four words that Grid uses precisely, because the rest of the chapter is built on them.

  • Grid lines — the invisible dividing lines that make up the grid, numbered starting at 1 (not 0). A grid with 3 columns has 4 vertical grid lines (think of 3 columns of text needing 4 fence-posts to mark their edges: left-of-col-1, between col-1/col-2, between col-2/col-3, right-of-col-3).
  • Grid tracks — the space between two adjacent grid lines. A "column track" is the space between two vertical lines; a "row track" is the space between two horizontal lines. When you set the width of a column, you are really setting the width of a track.
  • Grid cell — the smallest unit: one row track intersecting one column track, like a single box in a spreadsheet.
  • Grid gap — fixed empty space the browser inserts *between* tracks. Critically, gap only ever appears between tracks — never on the outer edge of the grid. We will return to this in the misconception section, because it trips up almost every beginner.

With that vocabulary, a "grid" is simply: a set of column tracks, a set of row tracks, and gaps between them — forming a matrix of cells that child elements get placed into.

Turning On Grid and Sizing Tracks with the fr Unit

Grid is switched on with a single declaration on the parent (container) element:

.container {
  display: grid;
}

That alone does very little visually — you still need to tell the browser how many tracks to create and how big each one should be. That's the job of grid-template-columns and grid-template-rows. You can size tracks in familiar units like pixels or percentages, but Grid also introduces a new unit built specifically for it: fr, short for "fraction." An fr track claims a share of whatever space is *left over* after every fixed-size track and every gap has already been subtracted.

.container {
  display: grid;
  grid-template-columns: 1fr 1fr 2fr;
}

Read this as "divide the leftover space into 4 equal parts (1+1+2=4), give one part to column 1, one part to column 2, and two parts to column 3." It behaves like sharing sweets by ratio, not by fixed size — which is exactly why it resizes gracefully when the browser window changes width, unlike a fixed-pixel layout.

Worked Example: Computing Fair-Share Widths With Gaps

Numbers make this concrete. Suppose a container is exactly 960px wide, with:

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

Follow the browser's own arithmetic, step by step:

  1. Subtract the gaps first. Three columns need only 2 gaps between them (never a gap before the first or after the last column). 2 gaps × 20px = 40px used by gaps.
  2. Find the leftover space. 960px − 40px = 920px remains to be shared among the fr tracks.
  3. Add up the fr shares. 1 + 1 + 2 = 4 total shares.
  4. Find the value of one share. 920px ÷ 4 = 230px per share.
  5. Multiply out each column. Column 1 = 1 × 230px = 230px. Column 2 = 1 × 230px = 230px. Column 3 = 2 × 230px = 460px.

Check: 230 + 230 + 460 + 40 (gaps) = 960px. Exactly matches the container width, as it must — fr never overflows or underflows a defined container, because it is defined as "whatever is left," not as an independent absolute size.

Mixing units is completely legal and common: grid-template-columns: 220px 1fr 1fr would first reserve a fixed 220px sidebar, then split whatever remains equally between two flexible columns — this is the standard pattern for a fixed sidebar next to fluid content.

Placing Items Precisely: Grid Lines and Spans

So far every track has filled itself automatically with whichever child came next. But Grid's real power is that any child can be told, explicitly, which grid lines to start and end at — letting one element span multiple tracks, which is how you build a "featured story" box on a news homepage or a tall product image in a shopping grid.

The property is grid-column: <start line> / <end line> (and the equivalent grid-row for the vertical direction). Remember: these are line numbers, counted from 1, and the end value is the line the item stops *before* — exactly like array slicing, where [2:4] takes indices 2 and 3 but stops before 4.

A 4-column, 3-row grid with numbered grid lines, showing an item placed with grid-column: 2 / 4 and grid-row: 1 / 3 Grid lines are numbered from 1, tracks sit between them grid-column: 2 / 4 grid-row: 1 / 3 1 2 3 4 5 1 2 3 4 4 columns need 5 vertical lines; 3 rows need 4 horizontal lines — the item spans from line 2 up to (but not including) line 4

In the diagram, the highlighted box starts at vertical line 2 and stops before vertical line 4, so it visually covers columns 2 and 3 — two column tracks merged into one wide cell. Vertically it starts at line 1 and stops before line 3, covering row tracks 1 and 2. A shorthand exists for spans: grid-column: 2 / span 2 means "start at line 2 and cover 2 tracks," which is often easier to read than counting the end line yourself.

Auto-Placement: Letting Grid Do the Work

Explicit placement is powerful, but most of the time you don't want to number every single child — you want the browser to lay them out automatically, the way a spreadsheet auto-fills rows. This is Grid's default behaviour, called auto-placement: children with no grid-column/grid-row of their own are dropped into the next empty cell, scanning left-to-right along a row, then wrapping to the next row, until all cells are filled — precisely how you'd fill in a printed timetable by hand.

Let's build a real six-day CBSE school timetable strip this way. The grid needs 7 columns (one narrow "period label" column plus 6 day columns for Monday–Saturday) and 3 rows (one header row plus two period rows):

.timetable {
  display: grid;
  grid-template-columns: 100px repeat(6, 1fr);
  grid-template-rows: 40px repeat(2, 60px);
  gap: 2px;
}

repeat(6, 1fr) is shorthand that expands to 1fr 1fr 1fr 1fr 1fr 1fr — six equal flexible day columns — and repeat(2, 60px) expands to 60px 60px, two equal-height period rows. Combined with the fixed 40px header row, that's 7 column tracks × 3 row tracks = 21 cells total. Now the matching HTML, written out in full so you can count it yourself:

<div class="timetable">
  <div class="corner"></div>
  <div class="day">Mon</div>
  <div class="day">Tue</div>
  <div class="day">Wed</div>
  <div class="day">Thu</div>
  <div class="day">Fri</div>
  <div class="day">Sat</div>
  <div class="period">1</div>
  <div class="cell">Maths</div>
  <div class="cell">Physics</div>
  <div class="cell">English</div>
  <div class="cell">Maths</div>
  <div class="cell">Chemistry</div>
  <div class="cell">Sports</div>
  <div class="period">2</div>
  <div class="cell">Physics</div>
  <div class="cell">Maths</div>
  <div class="cell">Computer Sci</div>
  <div class="cell">English</div>
  <div class="cell">Physics</div>
  <div class="cell">Library</div>
</div>

Count the opening <div> tags inside .timetable: 1 corner + 6 day + 1 period + 6 cell + 1 period + 6 cell = 21, matching the 7×3 = 21 cells the CSS declared. Auto-placement drops them in, in source order, row by row: the corner fills row 1 / column 1, the six day names fill the rest of row 1 (row 1 / columns 2–7), the first "1" label fills row 2 / column 1, the six Monday-to-Saturday subjects for period 1 fill the rest of row 2, and the same pattern repeats for period 2 in row 3. Because the count matches exactly, every cell in the 21-cell grid is occupied and nothing is left as an empty strip — this is the check you should run on any grid layout: multiply columns × rows, then count your children, and confirm they match (or that you've deliberately left cells empty).

Naming Regions with grid-template-areas

Line numbers are precise but not very readable — six months later, "grid-column: 2 / 4" tells you nothing about *what* is there. For page-level layouts (the "complex layouts" this chapter is named for), Grid offers a second, more legible placement system: grid-template-areas, where you literally draw the layout using words, and each word becomes a named region.

.page {
  display: grid;
  grid-template-columns: 200px 1fr;
  grid-template-rows: 60px 1fr 50px;
  grid-template-areas:
    "header header"
    "sidebar main"
    "footer footer";
}

.top    { grid-area: header;  }
.nav    { grid-area: sidebar; }
.content{ grid-area: main;    }
.bottom { grid-area: footer;  }

Each quoted string in grid-template-areas represents one row, and each word inside it represents one column's cell in that row. Repeating a name across adjacent cells (like "header header" spanning both columns, or "footer footer" spanning both columns) tells Grid to merge those cells into one region — this is a plain-English alternative to writing grid-column: 1 / 3. A child then attaches itself to a named region with a single line, grid-area: header, instead of juggling four separate line numbers. This is the standard way production sites build a header/sidebar/main-content/footer page skeleton, because renaming or reordering regions is as easy as rearranging the quoted strings — the visual shape of the CSS literally mirrors the visual shape of the page.

Responsive Grids Without Media Queries: repeat(), minmax(), auto-fit and auto-fill

The repeat() function has a second, more powerful mode: instead of a fixed number of tracks, you can ask the browser to create as many tracks as will fit, using the keyword auto-fit or auto-fill in place of a number — combined with minmax(), which sets a track's minimum and maximum size in one declaration.

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

minmax(200px, 1fr) means "never let this track shrink below 200px, but let it grow to take an equal fr share of any leftover space." The browser works out how many 200px-minimum tracks can fit before turning to the next row — no media query needed, no manual breakpoint guessing.

Here is exactly how the browser decides, with numbers. Container width = 900px, gap: 20px, minimum track width = 200px. The browser tests how many tracks of at least 200px, separated by 20px gaps, can fit into 900px:

  • 4 tracks need 4×200 + 3×20 = 800 + 60 = 860px — fits inside 900px. ✓
  • 5 tracks need 5×200 + 4×20 = 1000 + 80 = 1080px — does not fit. ✗

So the browser settles on 4 possible track slots. Now suppose your gallery only has 3 photos. This is exactly where auto-fit and auto-fill — which are often confused for being the same thing — produce visibly different results:

  • auto-fill keeps all 4 track slots in the grid, even the one with no photo in it, and still shares space equally among all 4: leftover width for tracks = 900 − (3 gaps × 20) = 840px, ÷ 4 tracks = 210px each. The 3 photos render at 210px wide, and a visible 210px-wide empty column is left at the end of the row.
  • auto-fit collapses any track with nothing placed in it down to 0 width, and hands its share of space to the tracks that do have content. With only 3 real tracks left: leftover width = 900 − (2 gaps × 20) = 860px, ÷ 3 tracks = ≈287px each. The 3 photos stretch to fill the entire row width, with no leftover gap.

Rule of thumb: use auto-fit when you want present items to stretch and fill all available space (most photo galleries, card grids); use auto-fill when you specifically want to preserve a consistent track size and are fine with visible empty slots (for example, a form grid where you want columns to always line up with a fixed width even when a row has fewer entries).

A Common Misconception: gap Is Not Margin

Students who have just learned the CSS box model (margin, border, padding) very often assume that gap on a grid container behaves like margin does on a single box — that it adds space around the outside edge of the whole grid, the way margin: 20px pushes a box away from its neighbours on all four sides. This is incorrect. gap only inserts space between tracks — never before the first track and never after the last one. A grid with grid-template-columns: 1fr 1fr 1fr; gap: 20px; has exactly 2 gaps (between column 1–2 and between column 2–3), not 4. If you want breathing room around the outer edge of the whole grid, you still need padding on the grid container itself — gap and padding are solving two different problems and are not interchangeable. A second, related mix-up: because array indices and most loop counters in programming start at 0, students often assume grid line numbering does too — but CSS Grid lines are deliberately numbered starting at 1, matching how a person would count columns aloud ("first column, second column…"), not how a program indexes an array.

Check Your Understanding

  1. A grid container is 760px wide, with grid-template-columns: 1fr 3fr; gap: 20px;. Compute the width, in pixels, of each of the two columns.
  2. A grid has grid-template-columns: repeat(4, 1fr). How many vertical grid lines does it have, and what numbers are they?
  3. You write grid-column: 3 / 6 on an item. Exactly how many column tracks does it span, and which ones?
  4. A gallery container is 700px wide with gap: 10px and repeat(auto-fit, minmax(150px, 1fr)). Show the arithmetic for how many track slots can fit.
  5. Explain, in one sentence, why auto-fill can leave a visible empty column in a row that auto-fit would not.

Answers: (1) 2 fr shares share (760 − 20) = 740px, so 1 share = 740/4 = 185px → column 1 = 185px, column 2 = 3×185 = 555px (check: 185+555+20=760 ✓). (2) 5 lines, numbered 1 through 5. (3) 3 tracks — columns 3, 4, and 5 (it stops before line 6). (4) 5×150+4×10=790>700 fails, 4×150+3×10=630≤700 fits, so 4 track slots. (5) Because auto-fill preserves every track slot that could fit even if it holds no content, while auto-fit deletes empty slots and redistributes their space to tracks that do have content.

Summary

CSS Grid gives you true two-dimensional layout control that Flexbox cannot: you define column tracks and row tracks on a parent with display: grid, size them with fixed units or the proportional fr unit (remembering that fr always divides only the space left after fixed tracks and gaps are subtracted), and space them apart with gap (which never touches the outer edge — that's what padding is for). Children can be left to auto-placement, which fills cells in source order the way you'd fill a printed table by hand, or pinned exactly with numbered grid lines like grid-column: 2 / 4, where lines are counted from 1 and the span stops just before the end number — or given a readable name via grid-template-areas for whole-page skeletons like header/sidebar/main/footer. For layouts that must adapt to any screen width without media queries, repeat(auto-fit, minmax(min, 1fr)) lets the browser calculate track counts itself, collapsing empty tracks to give remaining items more room, while its sibling auto-fill keeps every possible slot reserved even when unused. Mastering Grid means being able to look at any two-dimensional design — a timetable, a dashboard, a photo wall — and immediately see it as rows, columns, lines, and named areas, then translate that structure directly into CSS.

Practice Exercises

Now it is time to practice! Complete these challenges to solidify your understanding:

  • Exercise 1: Write a short program that demonstrates the core concept from this chapter. Test it with at least 3 different inputs.
  • Exercise 2: Find a real-world example where css grid: creating complex layouts is used in an Indian company (like TCS, Infosys, Flipkart, or ISRO). Write a paragraph explaining the connection.
  • Exercise 3: Create a mind-map connecting css grid: creating complex layouts to at least 3 other topics you have studied.
← SASS: Supercharged CSS with Variables and NestingFlexbox: Flexible Box Layout Explained →

Found this useful? Share it!

📱 WhatsApp 🐦 Twitter 💼 LinkedIn