Hey! Today I want to talk about Grids on the web. This article was inspired by our recent redesign of the marketing and authentication pages.



The new design is based on a 12-column grid, with three main sections:
- Full-width section (blue)
- Narrow section (pink)
- Off-center section (tilt)
So, how do you implement this in CSS? There are several ways, and we'll go through them taking a look at the pros and cons.
Life Before Grid: The Struggle
Before CSS Grid became the de facto layout system, developers had to come up with all sorts of creative (and sometimes painful) ways to make grids work.
When Tables Ruled the Web
The earliest method was to literally use HTML tables for page structure. A <table>
would define your rows and columns, and each <td>
cell would hold a piece of content.
<table width="100%">
<tr>
<td width="25%">Sidebar</td>
<td>Main content</td>
</tr>
</table>
It worked for rigid layouts, but it mixed presentation with content and was notoriously hard to maintain. There was no real responsiveness, just "stretchable" layouts, and iframes were a thing.
Note: you can read about this more here Tables for Layout? Absurd. I was surprised but there is even a "Detailed HTML Course Outline SECTION on FRAMES" page.
Speaking of maintainability and mixing presentation with content, here's a realistic example from the modern web. (Yes, we still use tables: in emails)
Float Like a Butterfly, Layout Like a Brick
Then came the era of floats. By floating elements left or right and manually calculating widths in percentages, you could simulate a grid. It was a huge improvement over tables, but still fragile. You needed clearfix hacks, and even the smallest DOM change could break your layout.
Soon after, we started to see the first CSS frameworks. One of the most popular was the 960 Grid System. It was based on a 960px-wide container divided into 12, 16, or 24 columns. You would add classes to your elements to specify how many columns they should span.


Interestingly, later versions of the 960 Grid System introduced subgrids and adaptive modes using Adapt.js. Before media queries were widely supported, this JavaScript library allowed you to load different CSS files based on the viewport width.
var ADAPT_CONFIG = {
// Where is your CSS?
path: 'assets/css/',
// false = Only run once, when page first loads.
// true = Change on window resize and page tilt.
dynamic: true,
// Optional callback... myCallback(i, width)
callback: myCallback,
// First range entry is the minimum.
// Last range entry is the maximum.
// Separate ranges by "to" keyword.
range: [
'0px to 760px = mobile.css',
'760px to 980px = 720.css',
'980px to 1280px = 960.css',
'1280px to 1600px = 1200.css',
'1600px to 1920px = 1560.css',
'1940px to 2540px = 1920.css',
'2540px = 2520.css'
]
};
Flexbox: The Cool Kid
When Flexbox appeared around 2012-2013, it completely changed the way we thought about layout. For the first time, we could distribute space and align elements dynamically, both vertically and horizontally, without resorting to floats or clearfix hacks.
.container {
display: flex;
gap: 1.5rem;
}
.col {
flex: 1;
}
Suddenly, layouts became fluid. Elements could grow or shrink automatically based on available space, and centering something both horizontally and vertically finally became a one-liner.
Flexbox was a huge step forward. It's simple to use and understand, and it works great for one-dimensional layouts, like navbars or cards, where you want to distribute space between items and align things in a row or column.
So what's the issue with Flexbox if it's so great? The problem is it's one-dimensional. Let's imagine you have a section, an aside, and a footer. You want the aside on the left, the section to take the rest of the space, and the footer at the bottom.
Flexbox has the concept of a gutter (the gap
property) and the flex
property to specify how much space an element should take. In theory, you can say flex: 8
for the aside and flex: 4
for the section, and it will take three times more space than the aside. But how do you specify the next row? You can't—if you add a footer with flex: 12
, it will be on the same row as the aside and section.
Again, if we want to make a grid, we have to bake in classes like col-12
(100% width), col-3
(25% width), and so on.
The difference between previous CSS frameworks and Flexbox is CSS became much more powerful. Now, we don't need hacks like clearfix; we can create flows and align things easily. Another important invention was media queries, so we can change the layout based on screen size. And preprocessors like SASS let us create mixins and functions to generate these classes.
Here is one of my old pet projects Tiny percentage SCSS flexbox grid system. The layout looks like this:
<div class="grid">
<div class="col-desk-12 col-tab-4 col-mob-2"></div>
<div class="col-desk-4 col-tab-5 col-mob-2"></div>
<div class="col-desk-3 col-tab-3 col-mob-4"></div>
<div class="col-desk-2 col-mob-2"></div>
<div class="col-desk-3 col-mob-2"></div>
<div class="col-desk-1 col-mob-2"></div>
<div class="col-desk-4 col-desk-shift-1 col-tab-shift-0 col-tab-6 col-mob-2"></div>
<div class="col-desk-4 col-mob-3"></div>
<div class="col-desk-2 col-mob-1"></div>
<div class="col-desk-1 col-mob-1"></div>
<div class="col-desk-1 col-mob-3"></div>
<div class="col-desk-1 col-mob-1"></div>
<div class="col-desk-2 col-mob-1"></div>
<div class="col-desk-2 col-desk-shift-1 col-tab-shift-0 col-tab-1 col-mob-1"></div>
<div class="col-desk-1 col-tab-7 col-mob-1"></div>
<div class="col-desk-3 col-tab-5 col-mob-4"></div>
</div>
The power of SASS here is that, using variables, you can change the number of columns, the gutter size, and the breakpoints.
// for each 3 breakpoints you can
$grid-columns: 12;
$grid-sideMargin: 40px;
$grid-gutter: 20px;
$grid-breakpoint: 768px;
Looks like a full grid system, right? But again, it's not a real grid, just a set of classes you use to create a grid. If you want to change the layout, you have to change the classes for each element.
CSS Grid to the Rescue
And finally, we have CSS Grid. Introduced around 2017, CSS Grid is a two-dimensional layout system that allows you to define both rows and columns in a single container.
.container {
display: grid;
grid-template-columns: repeat(12, 1fr); /* 12 equal columns */
gap: 1.5rem;
}
.col {
grid-column: span 4; /* Span 4 out of 12 columns */
}
CSS Grid introduces several new concepts: grid areas, grid lines, the fr
unit, and the ability to overlap elements. It also provides powerful alignment capabilities and handles complex layouts with ease. Essentially, it's a full layout builder. Unlike Flexbox, you can create the same layout using a combination of different Grid techniques. Let's take this as an example:
/* ------------------------------------- */
/* HEADER */
/* ------------------------------------- */
/* | */
/* SIDEBAR | CONTENT */
/* | */
/* ------------------------------------- */
/* FOOTER */
/* ------------------------------------- */
.grid-container {
display: grid;
grid-template-areas:
"header header"
"sidebar content"
"footer footer";
grid-template-columns: 200px 1fr;
grid-template-rows: auto 1fr auto;
gap: 1rem;
min-height: 100vh;
}
.header {
grid-area: header;
background: #f3f4f6;
padding: 1rem;
}
/* Other styles for sidebar, content, and footer */
As you can see, it's almost like a drawing. You can see the layout structure right in the CSS. You define areas like header
, sidebar
, content
, and footer
, and then assign those areas directly in the CSS.
Another powerful feature of CSS Grid is subgrid
, but let's talk about this using our redesign as an example.
Columns, Alignment, and Grid Magic
When we began redesigning our marketing pages, I knew CSS Grid would be a perfect fit. I wanted precise alignment of elements across different top-level sections, as well as their content and nested sections.
Here is a simple example of 12 columns grid:
.grid {
display: grid;
grid-template-columns: repeat(12, 1fr);
gap: 20px;
}
The difference between the layout I showed above (with a header, sidebar, content, and footer) and our layout is that we still want a flow-like structure. We want a full-width section, a narrow section, and an off-center section, but we don’t want to define extra rows—just columns. The great thing about grids is that they can automatically wrap to the next row as needed. It’s like Flexbox, but with extra power.
<div class="grid">
<div class="item span-2">2 columns</div>
<div class="item span-10">10 columns</div>
<div class="item span-12">12 columns (full width)</div>
</div>
.span-2 {
grid-column: span 2;
}
.span-10 {
grid-column: span 10;
}
.span-12 {
grid-column: span 12;
}
There are many ways in Grid to define your columns/rows. For example, instead of spanning columns, you can define the start and end lines. If you want a full-width section, you can use grid-column: 1 / -1;
(start from the first line and end at the last). For a narrow section, you can use grid-column: 3 / span 8;
(start from the third line and span 8 columns).
.full-width {
grid-column: span 12;
}
.narrow {
grid-column: 3 / span 8; /* Start at column 3 and span 8 columns */
}
.off-center {
grid-column: 3 / span 9; /* Start at column 2 and span 7 columns */
}
It looks simple, but the syntax can be confusing. For example, if you want your column to start from column 2
, you actually need to write grid-column: 3 / span 8;
because the first line is 1
.
Is there a way to simplify this? You can use CSS custom properties to define the start and end lines. So you can define --start
and --end
properties and use them in your CSS.
:root {
--start: 3;
--end: 10;
}
.narrow {
grid-column: var(--start) / var(--end);
}
But that feels a bit like a hack. There's a more elegant, native way: define names for the grid lines:
.grid {
display: grid;
grid-template-columns: [full-start] repeat(2, 1fr) [narrow-start] repeat(8, 1fr) [narrow-end] 1fr [off-center] 1fr [full-end];
gap: 20px;
}
.full-width {
grid-column: full-start / full-end;
}
.narrow {
grid-column: narrow-start / narrow-end;
}
.off-center {
grid-column: narrow-start / off-center;
}
Even better, with this approach, you can change everything about the layout in one place for different screen sizes using media queries.
@media ( max-width: 768px ) {
grid-template-columns:
[full-start]
1fr
[narrow-start]
repeat(10, 1fr)
[narrow-end off-center]
1fr
[full-end];
}
@media ( max-width: 480px ) {
grid-template-columns:
[full-start narrow-start]
repeat(4, 1fr)
[narrow-end full-end off-center];
}
The cool thing here is you can combine different areas into one. For example, you can combine [narrow-end]
and [off-center]
on the tablet breakpoint into one area like [narrow-end off-center]
, and on the mobile, you can combine all three into [full-end narrow-end off-center]
.
Subgrids: Grids All the Way Down
Subgrids is a relatively new feature. I remember that just a few years ago, it wasn’t supported widely, and I really struggled without it. To me, using grid layout without subgrids was almost pointless because it required hacks. Most website structures are far more complex than a single grid.
As you can see in the image above, you have a grid on the main wrapper component, then a section inside, and then cards and blocks inside the section. You want them all to align with the main grid. Without subgrid, you can't do this cleanly.
All you need to do is set display: subgrid
on the child grid, and it will inherit the grid structure from the parent grid.
.section {
display: grid;
grid-template-columns: subgrid;
gap: 20px;
}
.card {
grid-column: span 4;
}
Let's combine all these techniques into one example. Here, we have nested components that consume the main grid and also rearrange the layout for different screen sizes.
Key Takeaways
It's surprising how well CSS Grid solves so many issues we used to tackle with hacks. Here are my takeaways:
- CSS Grid sound complex, but to achieve most layouts, you only need a small part of the system. Many things you used to do in Flexbox also work in Grid, like
justify-content
,align-items
,gap
, and so on. - CSS Grid is not a silver bullet. There are still cases where Flexbox is a better fit, for example, for simple one-dimensional layouts like navbars or lists. But for complex layouts with multiple rows and columns, Grid is the way to go.
- You can name column ranges, which makes column assignment much more readable and easier to maintain.
- With media queries, you can change the whole grid structure in one place and rearrange the layout for different screen sizes. You can also combine different areas into one, making it even more flexible.
- With subgrids, you can make complex nested layouts and still keep everything aligned to the main grid.
Note: If you want to learn more about CSS Grid, I recommend resources like CSS Tricks Complete Guide to Grid and MDN Web Docs on CSS Grid.