CSS Grid is one of the greatest advances in the history of web layout. While Flexbox solves problems along a single axis, Grid thinks two-dimensionally from the start. This makes it the ideal solution for complex layouts that were previously only achievable with hacks, floats, or JavaScript.
When Grid was fully supported by all modern browsers, it permanently changed my way of working as a Frontend Engineer. Layouts became clearer, more logical, and significantly more maintainable. This door lays the foundation before we dive deeper into Grid features like minmax, auto-fit, auto-fill, or subgrid in the next steps.
Thinking in Columns and Rows: Tracks
Grid is based on so-called Tracks. These tracks are the fundamental building blocks of any Grid layout and are divided into two categories: Columns and Rows. Columns run vertically from top to bottom and define the width of content. Rows run horizontally from left to right and define the height. This two-dimensional way of thinking fundamentally distinguishes Grid from Flexbox, which always works along only one axis.
A simple example illustrates the concept:
.grid {
display: grid;
grid-template-columns: 200px 1fr 200px;
}
What is created with this simple definition? On the left, a fixed sidebar appears with exactly 200 pixels width. In the middle, a flexible main area extends, taking up all available space. On the right, there is another fixed area, also 200 pixels wide.
This is the core of Grid: You define the structure upfront, and the layout automatically results from it. There are no manual calculations, no complicated wrapper elements, and no nested containers that exist only to enable a layout. Grid consistently separates structure and content.
Units You Need to Know
px – Fixed Values
Pixel values are the simplest form of size specification in Grid. They are not flexible, but absolutely reliable and predictable. If a column should be exactly 250 pixels wide, it stays that way – regardless of viewport or other factors. This makes them ideal for sidebars, header elements, or UI components with fixed width.
fr – The Flexible Unit
The fr unit is the heart of Grid and one of the greatest innovations of the entire specification. fr stands for “fraction” and describes a proportional share of the available space. Unlike percentage values, fr automatically takes into account already assigned fixed sizes and gaps.
grid-template-columns: 1fr 2fr;
In this example, the available space is divided into three parts. The left area receives one part (1fr), the right area two parts (2fr). The result: The right area is exactly twice as wide as the left one – regardless of how wide the container is overall. This type of proportionality is significantly more complex to achieve with Flexbox and nearly impossible with percentages once fixed sizes are involved.
auto – Content Determines Size
The auto size is particularly intelligent: It adapts exactly to the content. This is extremely helpful when placing buttons next to each other that should only be as wide as their text. Or when a sidebar should have exactly the width of its content, but not a pixel more. Unlike min-content or max-content, auto behaves flexibly and can grow when more space is available.
Percentage Values
Percentage values work in Grid just as they do in other layout contexts. They are still useful, but in modern Grid layouts they are often replaced by the fr unit because it handles gaps and fixed sizes more elegantly. However, percentages have their place when you want to explicitly reference the container width, independent of other Grid tracks.
Auto-Placement – The Browser Fills the Gaps
One of the most powerful features of Grid is its ability to automatically place elements. Grid places elements row by row by default, from left to right, similar to how text flows in a document. This behavior is called Auto-Placement and works completely intuitively.
.grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
}
HTML:
<div class="grid">
<div>A</div>
<div>B</div>
<div>C</div>
<div>D</div>
<div>E</div>
</div>
Layout:
A B C
D E _
The behavior is intuitive and requires no additional configuration. The browser automatically arranges the elements, creates new rows as needed, and manages all placement.
But Grid also allows placing individual items specifically, without disturbing the automatic placement of other elements:
.item-c {
grid-column: 1 / 3;
}
With this instruction, element C now spans two columns. It starts at grid line 1 and ends at grid line 3, thus occupying the first two columns. What’s remarkable: The rest of the elements automatically rearrange themselves around this larger element. There are no complex calculations, no wrapper divs, and no manual adjustments. Grid manages the placement intelligently in the background.
Practical Example: The Classic Three-Column Layout
Many projects require a classic three-column layout: A left sidebar for navigation, main content in the middle, and a right sidebar for additional information or widgets. In the era before Grid, this layout was a challenge. With Flexbox it’s possible, but tedious to implement – especially when the columns have different heights or when the layout needs to work on different screen sizes.
With Grid, the entire problem is reduced to a few lines:
.layout {
display: grid;
grid-template-columns: 250px 1fr 250px;
gap: 2rem;
}
That’s it. Both sidebars receive a fixed width of 250 pixels each, the main content takes up all available space. The gap ensures even spacing between the columns. No hacks, no percentage calculations taking margins into account, no height problems from differently long content. Grid treats all three columns as equal tracks of a unified layout.
Responsive Without Media Queries? No Problem with Grid.
A common pattern in web development is a grid with three cards side by side. The following code defines exactly that:
.grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
}
On desktop screens, this works perfectly. But on small screens, the cards should typically take up 100% width and be stacked on top of each other. The classic solution would be a media query:
@media screen and (max-width: 600px) {
.grid {
grid-template-columns: 1fr;
}
}
This works, but it’s an explicit breakpoint definition. With Grid features like auto-fit and minmax – which we’ll cover in detail in Door 8 – it often works completely without media queries. The grid then automatically adapts to the available width and wraps when space is insufficient. But we’ll get to that tomorrow.
Row and Column Gaps
Previously, you had to “fake” spacing between Grid items via margins – a cumbersome and error-prone process. You had to set negative margins on the container or use complex :last-child selectors to prevent unwanted spacing at the edges. Grid solves this problem with a built-in solution:
.grid {
gap: 1rem;
}
The gap property defines the spacing between Grid tracks – both between columns and between rows. These spaces are only inserted between items, never at the outer edge of the container. The result is clean, predictable, and works reliably.
If you need different spacing for columns and rows, you can define the values separately:
column-gap: 2rem;
row-gap: 1rem;
This creates clear, clean gaps – without selector salad or margin hacks. The code remains maintainable and the visual structure is directly apparent from the CSS.
Grid Areas – Letting Layouts Speak
A particularly elegant pattern in Grid are the so-called Grid Areas. They not only make layouts functional, but also extremely readable. With Grid Areas, you define the layout visually in CSS – in a form that almost looks like an ASCII art diagram:
.grid {
display: grid;
grid-template-areas:
"header header"
"sidebar content"
"footer footer";
grid-template-columns: 200px 1fr;
}
This definition describes a layout with three rows. The first row contains only the header, which spans both columns. The second row is divided into sidebar and content area. The third row is completely occupied by the footer. The structure is immediately apparent – even for team members seeing the code for the first time.
The individual elements are then assigned to their respective areas:
.header { grid-area: header; }
.sidebar { grid-area: sidebar; }
.content { grid-area: content; }
.footer { grid-area: footer; }
The HTML remains completely free of any layout logic:
<div class="grid">
<header class="header"></header>
<aside class="sidebar"></aside>
<main class="content"></main>
<footer class="footer"></footer>
</div>
The order of elements in the HTML is completely independent of their visual position in the Grid. You could place the sidebar after the content in the HTML, and it would still appear on the left. This opens up completely new possibilities for responsive layouts and for the separation of semantic structure and visual presentation.
This pattern is particularly suitable for dashboards, admin interfaces, marketing pages, and complex landing pages. Wherever layouts are complex and the visual structure fundamentally changes between different screen sizes, Grid Areas show their strengths.
A Real Scenario: Forms and Multiple Columns
Forms with multiple columns are a pain point in many projects. You want to place related fields next to each other to save space and increase clarity. At the same time, some fields – such as textareas for longer inputs or important fields like email addresses – should take up the full width.
With Grid, this scenario is surprisingly easy to implement:
.form-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 1.5rem;
}
This code defines a two-column form with even spacing between fields. Each form field is automatically placed in the next available cell. For fields that should span both columns, you add a single line of CSS:
.full {
grid-column: 1 / -1; /* from first to last grid line */
}
This notation 1 / -1 means: Start at grid line 1 (far left) and end at grid line -1 (far right). The negative index -1 is a practical feature that always references the last line – regardless of how many columns the grid has. You could later expand the grid to three columns, and the .full class would still work without needing adjustment.
An input field with the .full class now spans both columns – cleanly, without tricks, and completely maintainable.
Grid Solves Problems Flexbox Cannot Solve
There are layout scenarios where Flexbox reaches its limits. A classic example: Cards should be arranged side by side in multiple columns and work with fixed or predictable heights.
With Flexbox, typical problems arise: Cards in a row can be brought to the same height, but multiple rows remain independent of each other. This leads to unsightly wrapping behavior where cards in different rows are no longer vertically aligned. Spacing between rows can only be implemented with margins, which then need to be manually compensated at the edges.
Grid solves all these problems with a simple definition:
.cards {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 1rem;
}
The heights of the cards are independent and stable. Each card occupies exactly one Grid cell, and the column widths remain consistent across all rows. Spacing is cleanly defined through gap and works reliably. The structure remains clear and maintainable – even when the number of cards or their contents change.
The fundamental difference is often described like this: Flexbox works “from the inside out” (content-based: the size of items is based on their content and influences the layout). Grid works “from the outside in” (layout-based: the structure is defined beforehand, and items adapt to this structure). That is the real paradigm shift that makes Grid an indispensable tool for two-dimensional layouts.
Conclusion
CSS Grid is the two-dimensional solution for layouts on the web. Whoever securely masters the basics – Tracks, fr units, Auto-Placement, and Grid Areas – can implement layouts more clearly, faster, and more robustly than with any other tool.
In Door 8, we continue with the Grid features that make Grid a real problem solver: minmax, auto-fit, auto-fill, and advanced Auto-Placement.
Comments