Learn CSS Grid: build a testimonial card layout step by step
A written lesson made by Unwatch from a free YouTube video, with the video's code, timestamps, a quiz and flashcards.
This lesson builds a testimonial card layout with CSS Grid, one property at a time: display grid, gap, grid-template-columns, spanning columns, placing items by line number, then media queries and grid-template-areas.
It suits you if you know basic CSS and the idea of a grid, but the properties still feel like guesswork.
When to watch instead: Watch the video if you want to see the browser's grid inspector overlay, which shows the lines and areas as you change values. A still lesson can't show that as well. The starter files are in the creator's GitHub repository, linked in the lesson.
Overview
This part introduces learning CSS Grid by simplifying it to avoid getting bogged down by its complexity. Kevin Powell explains the approach, presents the starter testimonial grid layout project, demonstrates applying display: grid, adds spacing using the gap property, and shows how to use browser developer tools (specifically Firefox's grid inspector) to debug and visualize grid layouts.
0:00 Introduction
- CSS Grid can feel overwhelming because of its many new properties, values, and units.
- While Flexbox is often the go-to tool, Grid can do things Flexbox cannot.
- By simplifying Grid and learning it in basic ways, it can actually be easier to use and learn than Flexbox.
1:01 How we're approaching this
- We will examine the layout using two different approaches:
- A natural starting point to understand the very basics of how to simplify Grid.
- An alternative approach to compare workflows.
- Both are simple, and you can choose which one speaks to you more depending on the layout type.
2:01 What we are working on
- Working with a testimonial grid layout (designed in Figma by Front-End Mentor).
- The project already has styling applied (fonts, colors, background images, shadows, etc.), but no grid properties have been applied yet.
- GitHub repo link:
https://github.com/kevin-powell/learn-grid-the-easy-way - The starting CSS file is
style.css(with finished versions also available in the repo).
2:39 What we are starting with
HTML structure for the testimonial grid:
<main class="testimonial-grid">
<article class="testimonial">
<!-- card content -->
</article>
<article class="testimonial">
<!-- card content -->
</article>
<!-- more articles... -->
</main>
- The
<main>element has the classtestimonial-grid. - Direct children are
<article>elements with the classtestimonial.
3:08 Declaring display grid
To turn the container into a grid, apply display: grid; to .testimonial-grid:
.testimonial-grid {
display: grid;
}
- Unlike Flexbox, which sometimes requires fine-tuning columns,
display: gridgenerally keeps things stable without changing much visually. - Because grid items (and flex items) no longer collapse margins, ensure your spacing between items is handled intentionally.
3:59 The gap property
- Avoid using margins for grid spacing.
- Always recommend
gapbecause it provides both vertical and horizontal spacing equally and cleanly. - Add
gapto.testimonial-grid:
.testimonial-grid {
display: grid;
gap: 1.5rem;
}
- Tip:
1.5remor2remworks well for spacing.
4:51 Use your grid inspector
- Always use browser dev tools when working with grid.
- Firefox is recommended for demos because its CSS Grid inspector features are very clear and easy to see (line numbers, area names, and visual overlays). Chrome and Safari also have robust grid inspectors.
- Steps in Firefox DevTools:
- Open Inspector.
- Select the
<main class="testimonial-grid">element. - Locate the Grid badge next to the element in the HTML tree and click it to toggle the grid overlay.
- In the Layout tab, check options like Display line numbers and Display area names to visualize the grid structure.
6:06 How many columns do you need
- When setting up a grid layout, one of the hardest things to know is how many columns you might need.
- Tip: Use a visual aid or wireframe tool (like Figma) to map out your container, rows, and columns before coding.
- In the current design, the layout has:
- A main container grid.
- Cards that span different widths (some take up more space, others split into multiple columns).
- Four total columns needed for the layout.
7:35 Declaring grid template columns
- To declare columns, use the
grid-template-columnsproperty inside your CSS selector. - Example using fixed pixel values:
.testimonial-grid {
display: grid;
gap: 1.5rem;
grid-template-columns: 100px 100px 100px;
}
- Example using percentages:
grid-template-columns: 25% 25% 25% 25%;
- Using the
fr(fraction) unit: Thefrunit is a grid-only unit that borrows from the flexbox algorithm. It represents a fraction of the available space, making it easy to divide space equally.
grid-template-columns: 1fr 1fr 1fr 1fr;
- Using the
repeat()function: To avoid repeating values manually (e.g.,1fr 1fr 1fr 1fr), userepeat()to specify the number of repetitions and the size:
grid-template-columns: repeat(4, 1fr);
- Tip: If you need 3 columns instead of 4, simply change the count:
grid-template-columns: repeat(3, 1fr);
10:28 You probably don't need to declare rows
- Implicit vs. Explicit Grid:
- Explicit grid: When you explicitly declare rows and columns (e.g., using
grid-template-columns). - Implicit grid: When rows are created automatically by the browser to accommodate content that overflows the explicit columns.
- Explicit grid: When you explicitly declare rows and columns (e.g., using
- Tip: Avoid declaring
grid-template-rowsunless you are working with very specific grids that require complex fineness and positioning. - For most layouts, you can get away with defining only
grid-template-columnsand letting the browser create rows implicitly as new items are added:
.testimonial-grid {
display: grid;
gap: 1.5rem;
grid-template-columns: repeat(3, 1fr);
}
- This reduces overhead and prevents issues where content overflows a fixed number of rows.
11:59 You probably don't need to declare rows (cont.)
- When using explicit columns (like
grid-template-columns: repeat(3, 1fr)), you specify their size. - Rows, however, can be implicitly created. They automatically match the height of the tallest item in that row, similar to how Flexbox works.
- While you can control auto rows and auto columns, keeping things simple by letting grid manage rows implicitly is usually best.
- When creating a display grid, you typically only need to set a gap and define your columns, then let rows handle themselves unless specific layout needs arise.
12:49 Spanning columns
Looking back at the layout design, there are specific testimonial cards that need to span across two columns each.
Creating a utility class for column spanning
Instead of finding and writing a separate class for each item, create a reusable utility class:
.grid-col-span-2 {
grid-column: span 2;
}
Tip: Kevin prefers prefixing class names with what they are used with (in this case, grid- for grid), creating a clear naming convention. While other layout methods or multi-column layouts exist, grid-column: span 2 is extremely explicit and easy to read.
Alternatively, CSS Grid supports property longhands:
grid-column-startgrid-column-endgrid-column(shorthand property)
Applying the class in HTML
Add the .grid-col-span-2 class to the specific articles in the HTML that need to span across two columns:
<main class="testimonial-grid">
<article class="testimonial flow bg-primary-400 grid-col-span-2">
<!-- content -->
</article>
<!-- ... -->
<article class="testimonial flow bg-neutral-100 text-neutral-900 grid-col-span-2">
<!-- content -->
</article>
</main>
15:10 Placing things in specific places on your grid
When building out layouts, items may sometimes end up in the wrong spots on the grid.
Using the last-child pseudo-class selector
For targeting the final testimonial card without creating unique classes for every item, use utility selectors like :last-child (or an nth-child selector):
.testimonial:last-child {
background: red !important;
}
Tip: Keeping the grid overlay visible in your browser inspector makes debugging layout issues significantly easier, especially when items aren't going where expected.
Placing items with line numbers
To place an item precisely across columns 4 to 5 using line numbers:
.testimonial:last-child {
grid-column: 4 / 5;
}
Seeing the line numbers enabled in your grid overlay makes positioning items on specific tracks much more intuitive.
17:52 Positioning items using line numbers
To control where an item is placed, we can use column and row line numbers.
Spanning Columns with grid-column
Instead of grid-column: span 2;, we can explicitly state the starting line number. For example:
.testimonial:last-child {
grid-column: 4;
}
This places the item starting at line 4 and leaves column 3 empty.
Understanding Grid Line Shorthand
grid-column is a shorthand property combining grid-column-start and grid-column-end:
.testimonial:last-child {
grid-column-start: 4;
}
If you specify only grid-column: 4;, it acts as grid-column-start: 4; and does not set a grid-column-end.
You can also specify both start and end values:
.testimonial:last-child {
grid-column-start: 3;
grid-column-end: 5;
}
This achieves the same result as grid-column: span 2;.
Why Prefer span 2?
- Versatility:
span 2works regardless of where the item is located in the grid or if its position changes later. - Simplicity: Avoids locking into rigid line numbers unless necessary.
20:01 Positioning Rows and Spanning
To move an item to the top of the grid and have it span multiple rows or lines:
.testimonial:last-child {
grid-column-start: 4;
grid-row-start: 1;
}
Grid Auto-Placement Behavior
When you define grid-row-start: 1; on an item that is already placed in column 4 (grid-column-start: 4), the grid automatically places it into the next available empty cell matching those constraints.
Example: Highlighting a Testimonial with Custom Grid Placement
To test and visualize how items flow into specific spots (for instance, the 3rd testimonial item in the DOM):
.testimonial:nth-child(3) {
background: red !important;
}
.testimonial:last-child {
grid-column-start: 4;
grid-row-start: 1;
}
- The last testimonial is forced to column 4, row 1.
- Grid intelligence automatically shifts other DOM elements into the next available empty cells (such as pushing the red 3rd testimonial down into the gap).
22:11 Setting Row End and Shorthand
To control how many rows an item occupies, use grid-row-end or the grid-row shorthand:
Option A: Using grid-row-end
.testimonial:last-child {
grid-column-start: 4;
grid-row-start: 1;
grid-row-end: 3; /* or 3 / 3 */
}
Option B: Using the grid-row Shorthand
.testimonial:last-child {
grid-column-start: 4;
grid-row: span 2;
}
Both options produce the exact same layout result, spanning the item across two rows from the starting point.
22:52 Setting Row End and Shorthand (Continued)
For the last grid item, you can specify row lines using line numbers:
.testimonial:last-child {
grid-column-start: 4;
grid-row-start: 1;
grid-row-end: 3;
grid-row: 1 / span 2;
}
- Alternatively, you can use
1 / span 2orspan 2 / span 2(orspan 2alone). - Tip: When declaring a starting line using
span, whether it's aspanor something else, it doesn't matter too much. If you like the idea of this being a utility class (as shown previously), you could create:
.grid-row-span-2 {
grid-row: span 2;
}
.grid-row-grid-row-span-2 {
grid-row: span 2;
}
- Warning on Shorthand Utilities: Because shorthand properties are a shorthand, they can sometimes get overwritten and run into finicky behavior. If you need a specific starting line or ending line, declare it directly on the selector instead of using a utility class. Use utility classes when you just want a natural flow.
23:55 Reviewing the Grid Setup (Desktop)
To recap the simplicity of the layout setup:
display: gridbecause we need it.gapbecause it makes spacing and life easier.- Set up your columns (
grid-template-columns: repeat(4, 1fr)). Do not set up rows unless you have to. Kevin didn't have to here, even though multiple rows had to be spanned. This is cleaner, easier to maintain, and easier to think about. - Use
spanto span columns or rows, regardless of where things start. - Only use specific start/end locations when you need precise placement. Always keep things as general, generic, and simple as possible, adding specificness only when required.
24:56 Working with Media Queries (Adding Responsive Design)
To make the layout responsive, open responsive mode in DevTools (shortcut: Shift + Cmd / Ctrl + M). In Chrome/Firefox, this is the device toolbar toggle icon.
Initial Setup Without Media Queries
Before adding a media query, we can remove the grid template columns from .testimonial-grid and set up a base mobile layout:
/* components */
.testimonial-grid {
display: grid;
gap: 1.5rem;
padding-block: 2rem;
width: min(95%, 70rem);
margin-inline: auto;
}
If you save this without columns declared, the layout still works because of auto-spacing, but column spacing can get mucked up since everything defaults to auto-placement. Therefore, declaring columns explicitly is helpful.
Let's add a media query for a minimum width of 40m:
@media (min-width: 40em) {
.testimonial-grid {
grid-template-columns: repeat(4, 1fr);
}
}
Refining the Media Query and Spans
If you test this at smaller screen sizes, the columns collapse into a single column. However, items like .testimonial:last-child (which uses grid-column-start: 4 and grid-row: 1 / span 2) will break if the screen size drops below the grid column count.
To fix this, move the specific column/row placements inside the media query so they only apply on larger screens:
@media (min-width: 50em) {
.testimonial-grid {
grid-template-columns: repeat(4, 1fr);
}
.testimonial:last-child {
grid-column-start: 4;
grid-row: 1 / span 2;
}
}
If your spans require 4 columns to exist, you need 4 columns for them to work properly; otherwise, elements will slot into empty spaces haphazardly. By wrapping both the grid template columns and the specific child placements inside the media query, everything falls into place at larger screen sizes and collapses cleanly at smaller screen sizes.
27:54 Alternatives and Summary
- There are ways of working without media queries (e.g., using
repeat(auto-fit, ...)), but on complex layouts like this, auto-fit can behave unpredictably. Keeping media queries for explicit layout shifts keeps things simple and robust. - All code and the GitHub repo are linked below in the video description for further review.
28:21 Simplifying Complex Layouts with Grid-Template-Areas
Grid template areas provide a much simpler way to work with CSS grid when dealing with complex layouts involving media queries.
To start fresh, delete the previous styling for .testimonial-grid, leaving only the media query:
@media (min-width: 50em) {
/* We will add grid styles here */
}
29:21 Declaring Grid Template Areas
When using grid-template-areas, each row is defined as a string of space-separated area names.
Step 1: Write the Template Areas
Inside .testimonial-grid at the desktop breakpoint, add the grid-template-areas property:
.testimonial-grid {
grid-template-areas:
'one one two five'
'three four four five';
}
- Syntax Rule: Always place each row on its own line enclosed in quotes. Area names must be strings (single or double quotes work), separated by spaces.
- Semicolon: Do not forget the semicolon after the final closing quote.
30:06 Visualizing Grid Areas in Developer Tools
Open the browser's developer tools and toggle the Grid visualization overlay on the .testimonial-grid element.
- Enable Display area names in the grid settings tab (available in both Chrome and Firefox).
- The layout now maps the elements to the corresponding named grid areas (
one,two,three,four,five).
30:40 Matching the Markup Design
Update the media query to apply the new grid template areas:
@media (min-width: 50em) {
.testimonial-grid {
grid-template-areas:
'one one two five'
'three four four five';
}
}
- How it works: Each set of quotes represents a row. Within each row, repeated names create column spans (e.g.,
'one one'spans two columns,'four four'spans two columns). - Grid Red Lines: Browser developer tools display solid red lines around named areas to highlight their boundaries and help debug placement.
33:10 Assigning Grid Items with :nth-child
To connect the HTML elements to the named grid areas, assign each child element to its corresponding area using grid-area:
.testimonial-grid > :nth-child(1) {
grid-area: one;
}
.testimonial-grid > :nth-child(2) {
grid-area: two;
}
.testimonial-grid > :nth-child(3) {
grid-area: three;
}
.testimonial-grid > :nth-child(4) {
grid-area: four;
}
.testimonial-grid > :nth-child(5) {
grid-area: five;
}
- This approach is especially powerful when working with components that have predefined classes (like user name, role, description, and image), allowing you to apply selectors directly to those existing classes rather than using
:nth-child.
33:21 Assigning Grid Items with :nth-child
To place items into specific grid areas, use the :nth-child() selector on .testimonial combined with the grid-area property.
.testimonial:nth-child(1) {
grid-area: one;
}
.testimonial:nth-child(2) {
grid-area: two;
}
.testimonial:nth-child(3) {
grid-area: three;
}
.testimonial:nth-child(4) {
grid-area: four;
}
.testimonial:nth-child(5) {
grid-area: five;
}
Note on syntax: Unlike string-based properties, grid-area values do not use quotation marks (e.g., use grid-area: one; instead of grid-area: "one";).
33:49 Media Queries with grid-template-areas
To change the layout at different screen sizes, define a media query and update grid-template-areas:
@media (min-width: 50em) {
.testimonial-grid {
grid-template-areas:
'one one two five'
'three four four five';
}
}
- Desktop layout: The layout rearranges smoothly when resizing the browser window, shifting items between columns and rows defined by the named areas.
- Inherited child rules: The child items (
.testimonial:nth-child(n)) never need to be rewritten or adjusted inside media queries. Once assigned to a named grid area (one,two, etc.), they automatically move to wherever those areas are placed in the grid template.
34:03 Creating Multiple Breakpoints
You can define multiple layouts at different screen widths by adding another media query with different templates.
For example, a smaller breakpoint at 30em:
@media (min-width: 30em) {
.testimonial-grid {
grid-template-areas:
'one one'
'two five'
'three five'
'four four';
}
}
@media (min-width: 50em) {
.testimonial-grid {
grid-template-areas:
'one one two five'
'three four four five';
}
}
- Items stack or expand across columns based purely on how you map the strings in
grid-template-areas. - If a named area (like
five) spans two rows, it automatically stretches vertically across both rows without touching the children's CSS.
36:01 Using grid-auto-columns for Uniform Sizing
By default, if you don't explicitly define column sizes alongside grid-template-areas, CSS Grid automatically sizes them based on the content distributed inside them.
If you want to ensure all implicitly generated columns remain a uniform size without having to manually define columns, use grid-auto-columns:
.testimonial-grid {
display: grid;
gap: 1.5rem;
grid-auto-columns: 1fr;
grid-template-areas:
'one'
'two'
'three'
'four'
'five';
}
grid-auto-columns: 1fr;ensures that any automatically generated columns created by your template areas take up an equal fraction (1fr) of available space.- This results in even columns and a balanced layout across breakpoints.
Key Takeaways
- Simplify CSS Grid: Focus on foundational properties like
display: grid,gap, and column definitions to avoid feeling overwhelmed by Grid's complexity. - Use the
gapProperty: Always usegapinstead of margins for grid spacing to apply clean, equal vertical and horizontal gaps between items. - Leverage Browser DevTools: Use browser grid inspectors (especially Firefox's) to visualize line numbers, area names, and grid overlays while debugging layouts.
- Rely on Implicit Rows: Avoid declaring explicit rows (
grid-template-rows); let the browser automatically create rows that match the height of their tallest items. - Define Columns Efficiently: Use the
frunit combined with therepeat()function (e.g.,repeat(4, 1fr)) to easily and cleanly set up equal-width grid columns. - Span Items Easily: Use utility classes with
grid-column: span 2orgrid-rowshorthand to make elements span multiple tracks without locking into rigid line numbers. - Employ Media Queries for Responsive Design: Wrap explicit column counts and layout adjustments inside media queries to ensure layouts collapse cleanly on smaller screens.
- Streamline Complex Layouts with Named Areas: Use
grid-template-areasand thegrid-areaproperty to visually map out rows and columns, making responsive layout shifts much easier to manage.
Check yourself
Quiz
1. Why does Kevin Powell recommend using the `gap` property instead of margins for CSS Grid layouts?
- It allows items to break out of the container boundaries
- It provides clean, equal vertical and horizontal spacing simultaneously
- It automatically calculates the optimal column count for mobile devices
- Margins are completely disabled when `display: grid` is active
Answer: It provides clean, equal vertical and horizontal spacing simultaneously
The gap property is always recommended because it provides both vertical and horizontal spacing equally and cleanly without the issues of margins.
2. What is the primary benefit of using the `fr` (fraction) unit combined with `repeat()` (e.g., `repeat(4, 1fr)`)?
- It forces the browser to create explicit row heights automatically
- It automatically switches to a flexbox layout on mobile viewports
- It easily and cleanly divides available space into equal-width columns
- It prevents text content from overflowing the grid cells
Answer: It easily and cleanly divides available space into equal-width columns
The fr unit represents a fraction of the available space, and repeat() avoids manual repetition, making it simple to divide space equally.
3. According to the lesson, what is the best practice regarding row declarations in most grid layouts?
- Always declare explicit row heights using pixel values to prevent layout shift
- Avoid declaring `grid-template-rows` and let the browser handle rows implicitly
- Always use `grid-template-rows: auto` for every row to match content
- Always declare row numbers equal to the number of DOM children
Answer: Avoid declaring `grid-template-rows` and let the browser handle rows implicitly
You should avoid declaring grid-template-rows unless working with very specific grids; letting rows be created implicitly reduces overhead.
4. When should you prefer using `grid-column: span 2` over explicit line numbers like `grid-column: 3 / 5`?
- When you want versatility so the span works regardless of where the item is initially placed
- When you need to explicitly lock the item into the first row
- When you want to override media query breakpoints completely
- When you want the item to change color based on its position
Answer: When you want versatility so the span works regardless of where the item is initially placed
Span is more versatile because it works regardless of where the item is located in the grid or if its position changes later, avoiding rigid line numbers.
5. What happens when you define `grid-row-start: 1;` on an item that already has a specific column constraint (like `grid-column-start: 4`)?
- All other grid items are forced into a single column
- The browser throws a syntax error and ignores the grid property
- The grid automatically places it into the next available empty cell matching those constraints
- The item vanishes from the DOM because row 1 is reserved
Answer: The grid automatically places it into the next available empty cell matching those constraints
When you constrain an item's column and row-start, the grid automatically places it into the next available empty cell matching those constraints.
6. Why is it important to wrap both explicit column counts and specific child placements (like last-child spans) inside your media queries?
- To prevent browser caching issues on mobile devices
- Because media queries cannot modify grid templates without child placements
- To ensure that CSS variables load in the correct order
- So that spans requiring 4 columns do not break when the screen shrinks and columns collapse
Answer: So that spans requiring 4 columns do not break when the screen shrinks and columns collapse
If screen sizes drop below the grid column count required by specific item spans, elements will break unless wrapped inside the corresponding media query.
7. When using `grid-template-areas`, how do you make a specific area span across two columns within a row string?
- By typing the area name twice separated by a space (e.g., 'one one')
- By setting `grid-column-span: 2` inside the template string
- By appending `*2` to the area name inside the quotes
- By declaring the area name twice in separate row strings
Answer: By typing the area name twice separated by a space (e.g., 'one one')
Repeated names within each row string create column spans (e.g., 'one one' spans two columns).
8. What is a major advantage of using `grid-template-areas` combined with `grid-area` on your child components when designing responsive layouts?
- Child items never need their CSS rewritten inside media queries; they automatically follow the updated template
- It forces all browsers to render grid overlays by default
- It automatically converts grid items into inline-flex elements
- It completely eliminates the need for media queries
Answer: Child items never need their CSS rewritten inside media queries; they automatically follow the updated template
Once child items are assigned to a named grid area, they automatically move to wherever those areas are placed in the grid template across different breakpoints without touching the children's CSS.
Flashcards
How do you turn a container into a CSS Grid?
Apply `display: grid;` to the container element.
What property should you always use for grid spacing instead of margins?
The `gap` property (e.g., `gap: 1.5rem;`), which cleanly handles both vertical and horizontal spacing.
Which browser is especially recommended for its clear CSS Grid inspector tools?
Firefox, because of its clear line numbers, area names, and visual overlays.
What CSS unit represents a fraction of available space in a grid?
The `fr` unit, which borrows from the flexbox algorithm to easily divide space.
How can you write `grid-template-columns: 1fr 1fr 1fr 1fr;` more concisely?
Use the repeat function: `grid-template-columns: repeat(4, 1fr);`.
Should you usually explicitly declare `grid-template-rows`?
No, you should generally avoid declaring rows and let the browser create them implicitly to match the tallest item.
How do you make a grid item span across two columns?
Use `grid-column: span 2;` (or create a reusable utility class with this property).
Why is `grid-column: span 2` often preferred over rigid line numbers?
It is more versatile and works regardless of where the item is located or if its position changes later in the DOM.
What property defines rows as strings of space-separated area names for complex layouts?
`grid-template-areas`, where each quoted string represents a row.
How do you assign an HTML element to a named grid area?
Use the `grid-area` property with the area name (without quotation marks), such as `grid-area: one;`.
What is a major benefit of using `grid-template-areas` with media queries?
Child elements (`.testimonial:nth-child(n)`) never need to be rewritten inside media queries; they automatically follow wherever the named areas move.
Want a lesson like this from your video?
Paste any public YouTube video or playlist. Your lesson is ready in a few minutes.