Laying Out a News Homepage
Open any news website on a laptop. At the top there is a header with the paper’s name. The big main story takes most of the width. On the right there is a “Trending” sidebar and an advertisement. At the bottom there is a footer. Now open the same site on a phone: everything is in one neat column.
In the CSS Grid Basics lesson you learned to make columns and rows and to place items with line numbers. That works, but for a whole page, line numbers get hard to read. In this lesson you will learn to draw the layout with words, make cards wrap on their own without any media query, rearrange the page for phones, align items inside cells, and put a grid inside a grid.
By the end you will build the homepage of a small city newspaper called Nagar News.
grid-template-areas: Draw Your Layout
With grid-template-areas you give each part of the page a name and then write a small “map” of the grid. Each string in quotes is one row. Each word inside the string is one cell.
Then, on each item, grid-area: name tells it which area to fill. Writing the same name in two cells makes that item cover both.
<style>
.page {
display: grid;
grid-template-columns: 3fr 1fr;
grid-template-areas:
"header header"
"main sidebar"
"main ads"
"footer footer";
gap: 8px;
}
.page > * { padding: 16px; border-radius: 6px; }
header { grid-area: header; background: #fecaca; }
main { grid-area: main; background: #fef08a; min-height: 160px; }
aside { grid-area: sidebar; background: #bbf7d0; }
.ads { grid-area: ads; background: #bfdbfe; }
footer { grid-area: footer; background: #e5e7eb; }
</style>
<div class="page">
<header>Header</header>
<main>Main story</main>
<aside>Trending</aside>
<div class="ads">Ads</div>
<footer>Footer</footer>
</div>
Read the map from top to bottom: the header covers both columns, the main story covers two rows on the left, the sidebar and ads sit on the right, and the footer covers the full width. Anyone reading your CSS can now see the layout at a glance.
Two rules for the map:
- Every row must have the same number of cells.
- Each named area must form a rectangle. An L shape is not allowed.
To leave a cell empty, write a full stop (.) in its place, for example "header .".
Named Lines (A Quick Look)
In the last lesson you used numbered lines like grid-column: 1 / 3. You can also give lines names by writing them in square brackets inside grid-template-columns. This is useful for article pages, where text stays in a narrow middle column and photos stretch wider.
<style>
.article {
display: grid;
grid-template-columns: [full-start] 1fr [text-start] 3fr [text-end] 1fr [full-end];
gap: 8px;
}
.article p {
grid-column: text-start / text-end;
background: #f3f4f6;
margin: 0;
padding: 10px;
}
.article .photo {
grid-column: full-start / full-end;
background: #a5b4fc;
padding: 30px;
text-align: center;
}
</style>
<div class="article">
<p>Heavy rain is expected in the city this weekend.</p>
<div class="photo">Wide photo of the clouds</div>
<p>Schools may stay closed on Monday if roads flood.</p>
</div>
You will not need named lines every day, but when you see square brackets in someone else’s grid code, now you know what they are.
Responsive Cards Without Media Queries
News sites show many small story cards. On a phone you want one card per row, on a tablet two or three, and on a laptop four or five. Grid can do this with one line:
grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
Let us read it piece by piece:
minmax(160px, 1fr): each column is at least 160px wide and at most one share of the free space.auto-fit: instead of a fixed number like 4, the browser fits as many columns as it can.
<style>
.news-cards {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
gap: 12px;
}
.news-cards article {
background: #fff7ed;
border: 1px solid #fdba74;
border-radius: 8px;
padding: 12px;
}
</style>
<div class="news-cards">
<article>Metro line opens</article>
<article>Board results out</article>
<article>Monsoon update</article>
<article>Local cricket final</article>
<article>New bus routes</article>
</div>
Press Try it yourself and make the result panel narrower and wider. The cards jump between rows on their own. No media query was needed.
auto-fit or auto-fill?
They are the same when there are enough items to fill a row. The difference shows when there are only a few items:
<style>
.fit, .fill {
display: grid;
gap: 8px;
margin-bottom: 20px;
border: 2px dashed #9ca3af;
}
.fit { grid-template-columns: repeat(auto-fit, minmax(100px, 1fr)); }
.fill { grid-template-columns: repeat(auto-fill, minmax(100px, 1fr)); }
.fit div, .fill div { background: #c4b5fd; padding: 12px; }
</style>
<p>auto-fit: the two cards stretch to fill the row</p>
<div class="fit"><div>Card 1</div><div>Card 2</div></div>
<p>auto-fill: empty columns are kept, cards stay small</p>
<div class="fill"><div>Card 1</div><div>Card 2</div></div>
| Keyword | When items do not fill the row | Use it for |
|---|---|---|
auto-fit | Empty columns collapse, so items stretch wider | Most card layouts |
auto-fill | Empty columns stay, so items keep their size | When cards must look the same size on every row |
Changing the Areas in a Media Query
For the main page layout you still want full control, and here template areas really shine. Write the phone layout first, as one column, and then redraw the map inside a media query for bigger screens. The HTML does not change at all.
<style>
.page {
display: grid;
gap: 8px;
grid-template-areas:
"header"
"main"
"sidebar"
"ads"
"footer";
}
@media (min-width: 600px) {
.page {
grid-template-columns: 2fr 1fr;
grid-template-areas:
"header header"
"main sidebar"
"main ads"
"footer footer";
}
}
.page > * { padding: 14px; border-radius: 6px; }
header { grid-area: header; background: #fecaca; }
main { grid-area: main; background: #fef08a; }
aside { grid-area: sidebar; background: #bbf7d0; }
.ads { grid-area: ads; background: #bfdbfe; }
footer { grid-area: footer; background: #e5e7eb; }
</style>
<div class="page">
<header>Header</header>
<main>Main story</main>
<aside>Trending</aside>
<div class="ads">Ads</div>
<footer>Footer</footer>
</div>
On a narrow screen, everything stacks in the order of the map. Above 600px, the same items move into two columns. You only changed one map, not every item. This mobile first approach was covered in the CSS Media Queries lesson.
Keep the HTML order sensible. Grid can show items in any order on screen, but screen readers and the Tab key still follow the HTML order. Put the main story before the sidebar in your HTML, and use grid only to change the visual position.
Aligning Items Inside Their Cells
By default a grid item stretches to fill its whole cell. Sometimes you want a small item, like a badge or an icon, to sit in the middle or a corner instead.
| Property | Set on | Direction |
|---|---|---|
justify-items | Container | Left to right (inline direction) |
align-items | Container | Top to bottom (block direction) |
place-items | Container | Both at once: align-items then justify-items |
justify-self, align-self | One item | Overrides the container for that item only |
The values are start, end, center and stretch (the default). In this example each cell is one column wide and 90px tall. The items shrink to the size of their text and sit in the middle of their cells, except the “Live” badge, which moves to the top right corner. Turn on the grid overlay in DevTools to see the cell edges:
<style>
.icons {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-auto-rows: 90px;
gap: 8px;
place-items: center; /* align-items + justify-items */
}
.icons div {
background: #fde68a;
padding: 8px 12px;
border-radius: 20px;
}
.icons .corner {
justify-self: end;
align-self: start;
}
</style>
<div class="icons">
<div>Sports</div>
<div>Politics</div>
<div class="corner">Live</div>
<div>Weather</div>
<div>Business</div>
<div>Tech</div>
</div>
place-items: center is one of the shortest ways in CSS to put something exactly in the middle of a box.
Nesting Grids
A grid item can also be a grid container for its own children. This is called a nested grid. Real pages do this all the time: the page is one grid, and inside the main area, the list of smaller stories is a second grid. Each grid only controls its own direct children. You will see a nested grid in the next example.
Real-Life Example: The Nagar News Homepage
Here is the full homepage. The page uses template areas that change at 700px. Inside main, the “more news” cards form a nested grid with auto-fit, so they wrap by themselves. The photo needs an internet connection.
<style>
body { margin: 0; font-family: Georgia, serif; background: #f9fafb; }
.page {
display: grid;
gap: 12px;
padding: 12px;
grid-template-areas: "header" "main" "sidebar" "ads" "footer";
}
@media (min-width: 700px) {
.page {
grid-template-columns: 3fr 1fr;
grid-template-areas:
"header header"
"main sidebar"
"main ads"
"footer footer";
}
}
header { grid-area: header; background: #991b1b; color: white; padding: 12px 16px; }
header h1 { margin: 0; font-size: 26px; }
main { grid-area: main; }
aside { grid-area: sidebar; background: white; padding: 12px; }
.ads { grid-area: ads; background: #fef3c7; padding: 12px; text-align: center; }
footer { grid-area: footer; text-align: center; color: #6b7280; font-size: 14px; }
.top-story { background: white; padding: 16px; margin-bottom: 12px; }
.top-story img { width: 100%; height: auto; display: block; }
/* a nested grid inside main */
.more-news {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 10px;
}
.more-news article { background: white; padding: 10px; font-size: 15px; }
aside ol { padding-left: 20px; margin: 0; }
</style>
<div class="page">
<header><h1>Nagar News</h1></header>
<main>
<article class="top-story">
<img src="https://picsum.photos/seed/news1/800/350" alt="Crowd at the new metro station">
<h2>New metro line opens for the public</h2>
<p>Thousands of commuters took the first ride on Sunday morning.</p>
</article>
<div class="more-news">
<article>State board results to be declared on Friday</article>
<article>City team reaches the cricket final</article>
<article>Monsoon arrives two days early</article>
</div>
</main>
<aside>
<h3>Trending</h3>
<ol>
<li>Petrol prices today</li>
<li>Exam timetable released</li>
<li>Weekend weather</li>
</ol>
</aside>
<div class="ads">Advertisement: Diwali sale at the city mall</div>
<footer>Nagar News, a sample site for learning CSS</footer>
</div>
Try these changes:
- Move the ads above the sidebar on big screens by swapping the words
sidebarandadsin the desktop map. - Add a fourth story card inside
.more-newsand watch the nested grid make room. - Change the desktop columns to
2fr 1frto give the sidebar more space.
Common Mistakes
- An area that is not a rectangle. If one name makes an L shape, or rows have different cell counts, the browser ignores the whole
grid-template-areasvalue. See the example below. - Forgetting
grid-areaon the items. The map does nothing until each item says which area it belongs to. Without it, items are placed one by one into the cells. - Putting quotes around the name in
grid-area. Writegrid-area: main;, notgrid-area: "main";. Quotes are only used ingrid-template-areas. - Using
repeat(auto-fit, 1fr). This is not valid.auto-fitneeds a minimum size, so always pair it withminmax().
In this broken map, main appears in two places that do not touch as a rectangle, so the layout is ignored:
<style>
.page {
display: grid;
grid-template-columns: 1fr 1fr;
grid-template-areas:
"header main"
"main footer"; /* main is not a rectangle */
gap: 8px;
}
.page > * { padding: 12px; background: #e0e7ff; }
header { grid-area: header; }
main { grid-area: main; }
footer { grid-area: footer; }
</style>
<div class="page">
<header>Header</header>
<main>Main</main>
<footer>Footer</footer>
</div>
Fixed: every area is a rectangle, and every row has two cells:
<style>
.page {
display: grid;
grid-template-columns: 1fr 1fr;
grid-template-areas:
"header header"
"main main"
"footer footer";
gap: 8px;
}
.page > * { padding: 12px; background: #e0e7ff; }
header { grid-area: header; }
main { grid-area: main; }
footer { grid-area: footer; }
</style>
<div class="page">
<header>Header</header>
<main>Main</main>
<footer>Footer</footer>
</div>
Practice Task: A Sports Desk Page
The sports editor of Nagar News wants a separate page for cricket. Use the starter code and complete the TODO steps.
- On phones, the order is header, live scores, news, footer (fans want scores first).
- From 650px wide, the news column (2fr) sits on the left and scores (1fr) on the right.
- The player cards inside the news section wrap by themselves using
auto-fitandminmax().
<style>
body { font-family: system-ui, sans-serif; margin: 0; }
.page { display: grid; gap: 10px; padding: 10px; }
.page > * { padding: 14px; border-radius: 6px; background: #e0f2fe; }
/* TODO 1: on phones, stack the areas: header, scores, news, footer */
/* TODO 2: from 650px wide, put news (2fr) and scores (1fr) side by side */
/* TODO 3: make .players a grid of cards using auto-fit and minmax(120px, 1fr) */
</style>
<div class="page">
<header>Sports Desk</header>
<section class="news">
<h2>Today's match report</h2>
<div class="players">
<div>Batter of the day</div>
<div>Best bowler</div>
<div>Best catch</div>
<div>Fastest fifty</div>
</div>
</section>
<aside class="scores">Live scores</aside>
<footer>Footer</footer>
</div>
Show one possible answer (try it yourself first!)
<style>
body { font-family: system-ui, sans-serif; margin: 0; }
.page {
display: grid;
gap: 10px;
padding: 10px;
grid-template-areas: "header" "scores" "news" "footer";
}
@media (min-width: 650px) {
.page {
grid-template-columns: 2fr 1fr;
grid-template-areas:
"header header"
"news scores"
"footer footer";
}
}
.page > * { padding: 14px; border-radius: 6px; background: #e0f2fe; }
header { grid-area: header; }
.news { grid-area: news; }
.scores { grid-area: scores; }
footer { grid-area: footer; }
.players {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
gap: 8px;
}
.players div { background: white; padding: 10px; border-radius: 6px; }
</style>
<div class="page">
<header>Sports Desk</header>
<section class="news">
<h2>Today's match report</h2>
<div class="players">
<div>Batter of the day</div>
<div>Best bowler</div>
<div>Best catch</div>
<div>Fastest fifty</div>
</div>
</section>
<aside class="scores">Live scores</aside>
<footer>Footer</footer>
</div>
Key Takeaways
grid-template-areaslets you draw the layout with names, andgrid-areaputs each item in its area.- Every named area must be a rectangle, and every row of the map must have the same number of cells.
repeat(auto-fit, minmax(160px, 1fr))makes cards wrap on their own, without media queries.- Redraw the map inside a media query to rearrange the page for bigger screens.
place-items,justify-itemsandalign-itemsposition items inside their cells.- Any grid item can be a grid itself. Each grid controls only its own children.