📐 Bootstrap Grid System Mastery
The grid is the single most useful thing Bootstrap gives you: a flexbox-powered, twelve-column layout engine that adapts from a phone to a widescreen monitor with a handful of classes. This lesson takes you from the three core building blocks to auto-layout, ordering, offsets, nesting, and the modern gutters API.
🎯 Learning Objectives
By the end of this lesson, you will be able to:
- Explain the roles of containers, rows, and columns and how they nest
- Use the 12-column model and the six responsive breakpoints to build layouts that reflow across devices
- Apply auto-layout,
row-cols-*, column ordering, and offsets to control placement - Control spacing with the Bootstrap 5 gutters API (
g-*,gx-*,gy-*) - Diagnose the most common grid mistakes and follow the do/don't rules for maintainable markup
Estimated Time: 35–45 minutes • Difficulty: Intermediate
Hands-on: Build a responsive product grid that shows 1, 2, 3, then 4 cards per row as the screen widens.
In This Lesson
Why a Grid at All?
Before CSS grid and flexbox were widely supported, laying out a page meant fighting with floats, clearfixes, and hard-coded pixel widths. Bootstrap's grid packaged the hard parts into a small vocabulary of classes so you can describe a layout — "this is a third of the width on desktop, full width on mobile" — and let the framework do the math.
💡 Analogy — city planning: Think of the grid as a city plan. The container is the city limits, setting the outer boundary. Rows are the streets that organize space horizontally. Columns are the building lots, each allocated a share of the block. The planner's rule — twelve lots per block — guarantees everything lines up no matter how big the city grows.
The grid you'll learn here is Bootstrap 5, which is built on flexbox. It has evolved a long way from the float-based grids of earlier versions:
float-based
xs·sm·md·lg"] --> B["Bootstrap 4
flexbox
+ xl, auto-layout"] B --> C["Bootstrap 5
flexbox
+ xxl, gutters API"]
Bootstrap 5's grid gives you six responsive breakpoints, flexbox alignment, auto-layout columns, a dedicated gutters API for spacing, plus ordering and offsetting — all without writing custom CSS.
Anatomy: Container, Row, Column
Every grid layout is built from three nested pieces. The order is strict: columns live inside rows, and rows live inside containers.
1. Containers
A container is the outermost wrapper. It centers your content and applies the responsive horizontal padding (half a gutter on each side). There are three flavors:
| Class | Behavior | Use when |
|---|---|---|
.container | Fixed max-width that steps down at each breakpoint | Standard centered page content (most common) |
.container-fluid | Always 100% wide | Full-bleed layouts, app shells, dashboards |
.container-{sm…xxl} | 100% wide until the named breakpoint, then fixed | Fluid on small screens, bounded on large ones |
<!-- Fixed-width, centered -->
<div class="container">...</div>
<!-- Always full width -->
<div class="container-fluid">...</div>
<!-- 100% wide until the md breakpoint, then fixed -->
<div class="container-md">...</div>
2. Rows
A row is a flex container for columns. It uses negative horizontal margins to cancel the container's padding so column edges align flush with the container edge. Always put a row inside a container (or another column).
3. Columns
Columns are the immediate children of a row and hold your actual content. You size them with the col-* classes described next.
📖 Key Terms
Gutter: the horizontal (and, in Bootstrap 5, vertical) space between columns, created by column padding.
Breakpoint: a screen-width threshold (e.g. 768px) at which the layout is allowed to change.
Auto-layout: letting Bootstrap divide the row equally among columns instead of naming a width.
The 12-Column System
Each row is divided into 12 equal units. A col-* class says how many of those units a column claims. The number 12 was chosen because it divides cleanly by 1, 2, 3, 4, 6, and 12 — so halves, thirds, and quarters all come out evenly.
| Layout | Classes | Each column's share |
|---|---|---|
| Two equal | col-6 + col-6 | 50% each |
| Three equal | col-4 × 3 | 33.3% each |
| Four equal | col-3 × 4 | 25% each |
| Sidebar + main | col-3 + col-9 | 25% / 75% |
<!-- Two equal columns -->
<div class="row">
<div class="col-6">Column 1</div>
<div class="col-6">Column 2</div>
</div>
<!-- Sidebar (25%) + main content (75%) -->
<div class="row">
<div class="col-3">Sidebar</div>
<div class="col-9">Main content</div>
</div>
⚠️ Watch the total
If the column numbers in a row add up to more than 12, the overflowing columns wrap onto the next line. That's sometimes intentional (a self-wrapping gallery) and sometimes a bug — always know which you meant.
Responsive Breakpoints
The real power comes from breakpoint-prefixed classes like col-md-6. A prefix says "apply this width from this screen width and up." Bootstrap is mobile-first: a class with no prefix applies to every size, and each prefix only adds overrides for larger screens.
| Breakpoint | Prefix | Applies from | Typical device |
|---|---|---|---|
| Extra small | col- (none) | 0px | Phones (portrait) |
| Small | col-sm- | ≥ 576px | Phones (landscape) |
| Medium | col-md- | ≥ 768px | Tablets |
| Large | col-lg- | ≥ 992px | Laptops |
| Extra large | col-xl- | ≥ 1200px | Desktops |
| XXL | col-xxl- | ≥ 1400px | Large desktops |
Stack several prefixes on one element to describe how a column reflows as the screen grows:
<div class="row">
<!--
full width on phones,
half width from sm up,
one third from md up,
one quarter from lg up
-->
<div class="col-12 col-sm-6 col-md-4 col-lg-3">Item 1</div>
<div class="col-12 col-sm-6 col-md-4 col-lg-3">Item 2</div>
<div class="col-12 col-sm-6 col-md-4 col-lg-3">Item 3</div>
<div class="col-12 col-sm-6 col-md-4 col-lg-3">Item 4</div>
</div>
✅ Mobile-first, in one sentence
Style the smallest screen first with an unprefixed class, then add prefixed classes only where a larger screen needs something different. You never write a "make it smaller for mobile" rule — mobile is the default.
Auto-layout, Order & Offsets
Auto-layout columns
Drop the number entirely and use bare col. Bootstrap splits the row equally among however many col elements it finds — add or remove one and the rest resize automatically.
<!-- Three equal columns, no arithmetic needed -->
<div class="row">
<div class="col">Equal</div>
<div class="col">Equal</div>
<div class="col">Equal</div>
</div>
<!-- One fixed column, the rest share what's left -->
<div class="row">
<div class="col-4">Fixed 4/12</div>
<div class="col">Auto</div>
<div class="col">Auto</div>
</div>
The row-cols-* classes set how many equal columns appear per row at each breakpoint — ideal for card galleries:
<!-- 1 per row on phones, 2 on md, 3 on lg, 4 on xl -->
<div class="row row-cols-1 row-cols-md-2 row-cols-lg-3 row-cols-xl-4 g-4">
<div class="col">Card 1</div>
<div class="col">Card 2</div>
<div class="col">Card 3</div>
<div class="col">Card 4</div>
</div>
Ordering
The order-* classes change the visual order without touching the HTML source order — great for putting a sidebar first visually on desktop while keeping the main content first in the markup for accessibility and SEO.
<div class="row">
<!-- Main content: first in HTML (good for screen readers),
but shown second (on the right) on lg screens -->
<div class="col-12 col-lg-8 order-lg-2">Main content</div>
<!-- Sidebar: second in HTML, shown first (on the left) on lg -->
<div class="col-12 col-lg-4 order-lg-1">Sidebar</div>
</div>
Offsets
The offset-* classes push a column to the right by inserting empty units before it — handy for centering or indenting content.
<div class="row">
<div class="col-md-4">Left</div>
<!-- 4 empty units, then the column -->
<div class="col-md-4 offset-md-4">Pushed right</div>
</div>
offset-md-4 leaves a four-unit gap.Nesting & the Gutters API
Nesting
You can put a new row inside a column to subdivide it. The nested row starts its own fresh 12-unit grid scoped to the parent column's width.
<div class="row">
<div class="col-sm-6">
<h3>Main column</h3>
<div class="row"> <!-- nested grid, 12 units of THIS column -->
<div class="col-6">Nested A</div>
<div class="col-6">Nested B</div>
</div>
</div>
<div class="col-sm-6">Other main column</div>
</div>
The gutters API
In Bootstrap 5, spacing between columns is controlled by g-* classes on the row, on a 0–5 scale. Use gx-* for horizontal-only and gy-* for vertical-only gutters — vertical gutters are what add breathing room between wrapped card rows.
<div class="row g-0">...</div> <!-- no gutters (edge to edge) -->
<div class="row g-2">...</div> <!-- small gutters -->
<div class="row gx-5 gy-3">...</div> <!-- wide horizontal, medium vertical -->
💡 Why gutters moved to the row
In Bootstrap 4 you fought gutters with negative margins and padding overrides. Bootstrap 5 drives them through the --bs-gutter-x / --bs-gutter-y CSS variables that the g-* classes set, so spacing is now declarative and consistent across the row.
Hands-on: Product Grid
🏋️ Build a responsive product grid
Objective: Create a grid of product cards that shows 1 card per row on phones, 2 on small, 3 on medium, and 4 on large screens, with even spacing and equal-height cards.
Starter (load Bootstrap 5 from the CDN in your page head):
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
Instructions:
- Wrap everything in a
.container. - Create a row that uses
row-cols-*to change how many cards appear per breakpoint. - Add a gutter class so the cards have space between them.
- Give each card
h-100so every card in a row matches the tallest. - Resize your browser (or use DevTools device mode) and watch the count change.
💡 Hint
The per-row count lives on the row, not the columns: row-cols-1 row-cols-sm-2 row-cols-md-3 row-cols-lg-4. Each card is just a bare <div class="col">. Add g-4 to the same row for spacing, and h-100 on each .card for equal height.
✅ Solution
<div class="container py-4">
<h2 class="text-center mb-4">Featured Products</h2>
<div class="row row-cols-1 row-cols-sm-2 row-cols-md-3 row-cols-lg-4 g-4">
<div class="col">
<div class="card h-100">
<div class="card-body">
<h5 class="card-title">Product One</h5>
<p class="card-text">$19.99</p>
<p class="card-text">A short product description.</p>
<button class="btn btn-primary mt-auto">Add to cart</button>
</div>
</div>
</div>
<!-- Repeat the .col block for each product -->
<div class="col">
<div class="card h-100">
<div class="card-body">
<h5 class="card-title">Product Two</h5>
<p class="card-text">$24.99</p>
<p class="card-text">Another short description.</p>
<button class="btn btn-primary mt-auto">Add to cart</button>
</div>
</div>
</div>
</div>
</div>
Because the count is declared once on the row, you can drop in as many .col cards as you like and they all follow the same responsive rules.
🎯 Quick Quiz
Question 1: Which nesting order is correct in the Bootstrap grid?
Question 2: A column has the classes col-12 col-md-6. How wide is it on a 500px-wide phone?
Question 3: In Bootstrap 5, which class adds vertical spacing between wrapped rows of cards?
Common Mistakes & Best Practices
| ✅ Do | 🚫 Don't |
|---|---|
| Put columns directly inside a row | Place a col straight inside a container with no row |
| Put rows inside a container (or a column) | Nest a container inside another container |
Nest a new row inside a column to subdivide | Nest a row directly inside a row without a wrapping column |
| Design mobile-first, then add larger breakpoints | Build desktop-first and bolt mobile on afterward |
Use g-* gutters for spacing | Add ad-hoc margins to columns to fake spacing |
Mistake: a row directly inside a row
<!-- WRONG: nested row is not wrapped in a column -->
<div class="row">
<div class="row">
<div class="col-6">A</div>
<div class="col-6">B</div>
</div>
</div>
<!-- RIGHT: wrap the nested row in a column -->
<div class="row">
<div class="col-12">
<div class="row">
<div class="col-6">A</div>
<div class="col-6">B</div>
</div>
</div>
</div>
Mistake: columns with no row
<!-- WRONG: columns are not inside a row -->
<div class="container">
<div class="col-6">A</div>
<div class="col-6">B</div>
</div>
<!-- RIGHT -->
<div class="container">
<div class="row">
<div class="col-6">A</div>
<div class="col-6">B</div>
</div>
</div>
⚠️ Keep the grid semantic
Grid classes are for layout, not meaning. Rather than putting col-md-9 straight on a <main>, wrap it: <div class="col-md-9"><main>…</main></div>. Your document keeps clean landmark elements and the layout stays flexible.
Summary & Quiz
🎉 Key Takeaways
- The grid nests strictly: container → row → column.
- Every row is 12 units;
col-*classes claim a share and must total 12 or they wrap. - Bootstrap is mobile-first — unprefixed classes apply everywhere; prefixes add rules for larger screens.
- Use auto-layout (
col), row-cols, order, and offsets to place content without custom CSS. - Spacing is the gutters API:
g-*,gx-*,gy-*on the row.
📚 Further Reading
🚀 What's Next?
With layout under control, the next lesson turns to the pieces you'll place inside the grid — Bootstrap Components and Utilities: navbars, cards, modals, forms, and the utility classes that let you style them without writing CSS.
🎉 Nicely done!
You can now reason about any Bootstrap layout by asking three questions: which container, how many units per column, and at which breakpoint does it change.