The Grid Legacy: An Artisanal Image Galler y for the Web
Web development—and CSS specifically—has advanced to a point where layouts can now be crafted to resemble print design. This article walks through a practical, handcrafted layout implementation, one technically from a real client project, that exploits the power of CSS Grid. We’ll cover two significant applications: first, staging a fixed grid with explicit start and end points for elements; and second, using CSS Grid template areas to reorder a simple HTML layout without altering the underlying structure.
A stop-by visual of the target design reveals desktop and mobile variants, each with an intentionally irregular, overlapping image grid. These aren't uniform rows; instead, they intermingle narrow text columns and oversized numbering, creating an art-directed canvas that pushes past the limitations of historical layout methods.
Since CSS Grid is now broadly safe to use, setting up the basics requires a parent container and the images themselves—a stark contrast to former techniques requiring cumbersome wrapping divs. While creating the markup, the secret lies in the reusable components: each image receives a CSS class-index, while the container includes both a base grid utility and a modifier to indicate its specific variant.
For the desktop variant, adjusting which images appear wasn't as simple as moving a file. For instance, accommodating a fourth image in the right location on desktop versus mobile required duplicating it in the HTML. Only the mobile version is visible by default, while the desktop version is toggled with a media query. Critically, any version marked with display:none gets properly hidden from both visual and screen-reader users.
Getting the Grid Map Right
To transform an uneven design into CSS Grid architecture, you have to find the proportional grid that sits underneath the artwork. Without the luxury of an official design grid, the process becomes a matter of approximation. Trusty lines over a mockup and a bit of "crooked math" reveal dimensions. The goal is to find a shared, divisible number for all measurements—eventually leading to the smallest common makeup.
For this particular layout, I determined a structure of 14 columns and 7 rows for mobile, using gap for consistent spacing. These four lines establish the container fully:
.grid {
display: grid;
}
.image-grid-3-m4 {
grid-template-rows: repeat(7, 1fr);
grid-template-columns: repeat(14, 1fr);
gap: 0.5rem;
}
From here, the automatic layout engine fills top-down, placing images one cell at a time. While effective for standard galleries, this CSS grid case demands nothing short of defined territory. Achieving the staggered look hinges entirely on the explicit grid-column and grid-row properties. By setting stretch marks (the same dimensions from before) to each item, we override the automatic placement. Instead of specifying endpoint numbers through multiple lines of code, using a span syntax keeps the intention readable—for example, telling the big block at the top-left to grid-column: span 6 and grid-row: span 4.
.image-0 {
grid-column: 1 / span 6;
grid-row: 1 / span 4;
}
The layout then needs visual protection from distortion. A few CSS commands allow us to manage this elegantly:
img {
object-fit:cover;
height: 100%;
width: 100%;
}
Spreading the width and height to 100%, these objects can now fill their containers entirely. Using object-fit: cover instructs the browser to crop content that doesn't match the aspect ratio. This means clients have to knowingly choose images with an object-fitting mindset. For elements like the top-right square image, the coding team can use the aspect-ratio property to enforce that square shape, creating a forced art crop beyond CSS's simple object-fit capabilities. Be mindful: aspect-ratio still has some legacy support gaps on iOS and old browsers, which may mean using a polyfill.
Responsive Re-configuration
On a desktop viewport, you break away from the smaller cell logic. A media query will trigger: redefining the grid definition and the start/end points for each element based on the wider boundaries. This is the same calculation process as the mobile step. Simply placing new start lines and spans inside a media query updates all positioning metrics.
See the Pen [Untitled [forked]](https://codepen.io/smashingmag/pen/yLqEgdj) by Pfenya.
Putting it into a flexible-width wrapper makes images resize their aspect-ratio automatically, yielding a fully animated grid for any breakpoint. The initial grunt work—understanding explicit positioning syntax and re-positioning margins—clicks into place as an instant unlock of artistic possibilities. While it isn't always apt for ubiquitous reusable components, this technique adds a layer of craft to complicated cases otherwise unsolvable with standard block flow.
Arranging the Magazine Component
With the image composition handled, we shift to arranging the component’s structural elements. This is where grid-template-areas really earns its keep. It lets us define a visual map of the layout directly in CSS, avoiding extra wrapper divs and duplicated HTML across breakpoints.
The component itself is minimal: a few images, the number in a white box, and the text block. Depending on the viewport, the images may split into separate sections.
The markup stays lean—one outer container and one div per logical part.
<div class="container">
<div class="images"></div>
<div class="numbering"></div>
<div class="text"></div>
<div class="single-image"></div>
</div>
On mobile, the number sits above the images. Because CSS Grid supports layering (using the same overlap technique from the images), we can place the number on top. Controlling the stack order is straightforward: the higher z-index wins.
We’ll create two grid areas: the top area holds the images plus the number (as a largely transparent layer, with the white box itself sized and centered via flexbox), and the bottom area contains the text. Rather than juggling raw row and column numbers, grid-template-areas lets us name cells and assign elements by name. It’s easier to read and maintain.
.container {
display: grid;
grid-template-columns: 1fr;
grid-template-rows: repeat(2, 1fr);
gap: 1.2rem;
grid-auto-flow: row;
grid-template-areas:
"numerology"
"text";
}
.images { grid-area: numerology; }
.numbering { grid-area: numerology; }
.text { grid-area: text; }
.single-image {display:none}
In the container, we set up a two-row grid and name each row: Numerology for the first, Text for the second. Then we use the grid-area property on each child to drop it into the right named slot.
Adapting to the desktop layout follows the same logic. The overall site already runs on a 14-column grid, and comparing the design against that grid shows which widths each block should occupy.
We override the area map inside a media query and introduce more specific names for the wider layout.
.container { display: grid;
grid-template-columns: repeat(14, minmax(0, 1fr));
grid-template-rows: repeat(2, 1fr);
gap: 1.2rem;
grid-auto-flow: row;
grid-template-areas:
"images images images images images . numbering numbering numbering single-image single-image single-image single-image single-image"
"images images images images images . text text text single-image single-image single-image single-image single-image";
}
.images { grid-area: images; }
.numbering { grid-area: numbering; }
.text { grid-area: text; }
.single-image { grid-area: single-image; }
Admittedly, the syntax here is verbose—grid-template-areas doesn’t support the repeat() shorthand that columns have. But breaking it down: the first five columns belong to images in both rows, a period marks an empty column, the next three columns cover numbering in the first row and text in the second, and the last five columns are single-image. For fiddly maps like this, an online visual grid generator can help you compose and copy the CSS.
Roughly 20 lines of CSS rearrange the whole component with zero changes to the HTML. The same technique extends to design variants (versions 2 and 4). Adding a modifier class like .version-a or .version-b to the container lets you swap in an alternate area map.
.version-1 {
grid-template-areas:
"images images images images images . numbering numbering numbering single-image single-image single-image single-image single-image"
"images images images images images . text text text single-image single-image single-image single-image single-image";
}
.version-1 .single-image {
grid-area: single-image;
display:block;
}
.version-2 {
grid-template-areas:
". numbering numbering numbering . . images images images images images images images images"
". text text text . . images images images images images images images images";
}
This approach removes almost all the pain of reordering content between breakpoints or shareable HTML templates. The same trick applies beyond magazines—an e-commerce product detail page, for instance, benefits greatly from defining named areas and moving elements based on context. The mental shift is that HTML no longer dictates visual order; the grid does. With container queries and layers on the horizon, flexibility like this is only growing.
The finished product, with all pieces combined:
See the Pen [Untitled [forked]](https://codepen.io/smashingmag/pen/VwBdpma) by Pfenya.
You can see the technique live on the Duftwelten page.




