I recently had to craft a newspaper-like design that featured multiple row and column spans with divider lines in between them. Take a look at the mockup graphic here and see if it makes you sweat at all. If you’re like me, you have been around a while and know just how difficult this would have been with old layout techniques.
The project came with a few requirements:
- Show the outlines of the grid
- Columns can be wider, or longer than others
- Divider lines must be shown between the various blocks
CSS Grid: Teaching an old layout new tricks
Newspaper layouts can cause headaches because everyday CSS is one-dimensional, meaning that elements flow on either a horizontal or vertical axis. Even modern flexbox layout is still uni-directional.
For a layout like this, we would almost want the properties that good ol’ HTML tables once provided: things like row and column spans to stretch cells in all directions. We would also want the benefits of modern day CSS, with all the responsiveness and flexible boxes that can grow to fill available space.
CSS grid combines the best of tables with the best of flexible boxes. In fact, grid’s even better because it provides the grid-gap
property for creating gutters between cells while taking available space into account. Powerful as this may be, how can we create divider-lines exactly in the middle of those gutters?
Let’s look at three techniques to make that happen.
What we’ll create
First, we will build a simplified version of the newspaper design that’ll help illustrate the crux of the three different techniques that we’re going to cover. A deceptively easy design, one would say.
Technique 1: The faux column
This solution creates “faux” columns that allow us to draw vertical lines, and then place a grid on top. Horizontal dividers are painted if needed. The “faux” columns are created by using pseudo selectors in the grid container.
<div class="frontpage">
<div class="fp-cell fp-cell--1">
<div class="fp-item">1</div>
</div>
<div class="fp-cell fp-cell--2">
<div class="fp-item">2</div>
</div>
<div class="fp-cell fp-cell--3 fp-cell--border-top">
<div class="fp-item">3</div>
</div>
<div class="fp-cell fp-cell--4 fp-cell--border-top">
<div class="fp-item">4</div>
</div>
</div>
See the Pen
Newspaper-design, ‘faux-column’ technique by Marco Troost (@marco-troost)
on CodePen.
Setting up the lines between the columns
Let’s create a three-column container using display: grid
and pseudo-selectors (:before
and :after
) to create two columns that fill 100% of the container’s height.
.frontpage {
position: relative;
display: grid;
/* Three columns */
grid-template-columns: 1fr 1fr 1fr;
grid-column-gap: 32px;
border: 1px solid transparent;
border-top: 1px solid #DADCE0;
border-bottom: 1px solid #DADCE0;
overflow: hidden;
}
/* Two faux columns */
.frontpage:before,
.frontpage:after {
position: absolute;
top: 0;
height: 100%;
content: '';
width: calc(33.3% - 4px);
}
.frontpage:before {
left: 0;
border-right: 1px solid #DADCE0;
}
.frontpage:after {
right: 0;
border-left: 1px solid #DADCE0;
}
Note: 33% of the container doesn’t take the gutter width into account, so you’ll have to compensate accordingly.
This is calculated as:
33% minus (gutter-width divided by (amount of gutters times amount of gutters)) divided by amount of gutters)
Or, with actual numbers:
33% - (32 / (2* 2)) / 2 = 4
We could use one pseudo-selector instead:
.frontpage {
position: relative;
display: grid;
grid-template-columns: 1fr 1fr 1fr;
grid-column-gap: 32px;
border: 1px solid transparent;
border-top: 1px solid #DADCE0;
border-bottom: 1px solid #DADCE0;
overflow: hidden;
}
.frontpage:before {
box-sizing: border-box;
position: absolute;
top: 0;
height: 100%;
content: '';
left: calc(33.3% - 5.3px);
width: calc(33.3% + 10.7px);
border-left: 1px solid #DADCE0;
border-right: 1px solid #DADCE0;
}
See the Pen
Newsgrid-layout ‘faux-columns’ (using only :before) by Marco Troost (@marco-troost)
on CodePen.
Note: A different calculation is needed when using only one pseudo-selector: One for positioning, and one for width.
The width is calculated as:
33% plus (amount of gutters times gutter-width) / (amount of gutters times amount of columns)
Again, with actual numbers:
33% + (2 * 32) / (2 * 3) = 10.7
The position is calculated as:
33% minus (amount of gutters times gutter-width) / (amount of gutters times amount of columns) divided by 2)
Making the grid
The design consists of four blocks of content. We’re going to place them in the container and give them a modifier class for future reference while making sure their z-index
is higher than the pseudo-selectors of the grid.
<div class="frontpage">
<div class="fp-cell fp-cell--1"></div>
<div class="fp-cell fp-cell--2"></div>
<div class="fp-cell fp-cell--3"></div>
<div class="fp-cell fp-cell--4"></div>
</div>
Now let’s set the background color for the cells (.fp-cell
) to white. This way, the vertical lines won’t show through. We can also set the vertical padding for the cell to 16px in order to match half of the gutter.
The first and second content blocks should get their own unique spans as shown in the design. The first block spans all the way down and the second block spans the second and third columns.
.fp-cell {
position: relative;
z-index: 2;
padding: 16px 0;
background-color: #fff;
}
/* Span all the way down! */
.fp-cell--1 {
grid-row: 1 / span 2;
}
/* Span the second and third columns */
.fp-cell--2 {
grid-column: 2 / span 2;
}
Vertical line dividers
If you look at the design, only the last two cells need a horizontal border. We can give ’em a sweet modifier class.
<div class="frontpage">
<div class="fp-cell fp-cell--1"></div>
<div class="fp-cell fp-cell--2"></div>
<div class="fp-cell fp-cell--3 fp-cell--border-top"></div>
<div class="fp-cell fp-cell--4 fp-cell--border-top"></div>
</div>
.fp-cell--border-top:before {
content: '';
position: absolute;
top: 0;
left: -16px;
right: -16px;
border-top: 1px solid #DADCE0;
}
The negative margins are half of the gutter width.
Technique #2: Using background-color
Another way to create the dividers is to utilize the grid-gap
property. This solution doesn’t necessarily create a “real” distance between cells, but rather leaves some blank space where the background-color
of the grid can shine through. The gutter width is delegated to padding within the grid cells.
<div class="container">
<div class="frontpage">
<div class="fp-cell fp-cell--1">
<div class="fp-item">1</div>
</div>
<div class="fp-cell fp-cell--2">
<div class="fp-item">2</div>
</div>
<div class="fp-cell fp-cell--3">
<div class="fp-item">3</div>
</div>
<div class="fp-cell fp-cell--4">
<div class="fp-item">4</div>
</div>
</div>
</div>
.container {
overflow-x: hidden;
border-top: 1px solid #DADCE0;
border-bottom: 1px solid #DADCE0;
}
.frontpage {
position: relative;
display: grid;
grid-template-columns: 1fr 1fr 1fr;
grid-gap: 1px;
margin: 0 -16px;
background-color: #DADCE0;
}
.fp-cell {
background-color: #fff;
padding: 16px;
}
.fp-cell--1 {
grid-row: 1 / span 2;
}
.fp-cell--2 {
grid-column: 2 / span 2;
}
.fp-cell--3 {
grid-column: 2;
}
.fp-item {
background-color: #efefef;
display: flex;
align-items: center;
justify-content: center;
min-height: 200px;
height: 100%;
}
See the Pen
Newspaper-design, background-color technique by Marco Troost (@marco-troost)
on CodePen.
Since all cells have an extra 16px of horizontal padding, the grid needs to be offset by just as much. A wrapper container will take care of the overflow.
<div class="container">
<div class="frontpage">
<!-- ... -->
</div>
</div>
.container {
border-top: 1px solid #DADCE0;
border-bottom: 1px solid #DADCE0;
overflow-x: hidden;
}
.frontpage {
position: relative;
display: grid;
grid-template-columns: 1fr 1fr 1fr;
grid-gap: 1px;
background-color: #DADCE0;
margin: 0 -16px;
}
Technique #3: Creating a cell border
This solution appends a right and bottom border to each cell. Like the last example, the grid-gap
is mimicked by adding padding to the cell content. That means it also needs to be wrapped in an extra container.
<div class="container">
<div class="frontpage">
<div class="fp-cell fp-cell--1">
<div class="fp-item">1</div>
</div>
<div class="fp-cell fp-cell--2">
<div class="fp-item">2</div>
</div>
<div class="fp-cell fp-cell--3">
<div class="fp-item">3</div>
</div>
<div class="fp-cell fp-cell--4">
<div class="fp-item">4</div>
</div>
</div>
</div>
.container {
border-top: 1px solid #DADCE0;
overflow-x: hidden;
}
.frontpage {
margin: 0 -17px 0 -16px;
position: relative;
display: grid;
grid-template-columns: 1fr 1fr 1fr;
}
.fp-cell {
padding: 16px;
background-color: #fff;
border-right: 1px solid #DADCE0;
border-bottom: 1px solid #DADCE0;
}
.fp-cell--1 {
grid-row: 1 / span 2;
}
.fp-cell--2 {
grid-column: 2 / span 2;
}
.fp-cell--3 {
grid-column: 2;
}
.fp-item {
background-color: #efefef;
display: flex;
align-items: center;
justify-content: center;
min-height: 200px;
height: 100%;
}
See the Pen
Newspaper-design, ‘cell-border’-technique by Marco Troost (@marco-troost)
on CodePen.
As mentioned, each cell is given a border on the right and on the bottom. The main trick here is the use of the (asymmetrical) negative margin on the grid. This is needed to compensate for the cell’s right border.
.frontpage {
margin: 0 -17px 0 -16px;
position: relative;
display: grid;
grid-template-columns: 1fr 1fr 1fr;
}
Conclusion
Occam’s razor stipulates that the simplest solution wins. In our case, that’s technique number two. But then again, the other solutions have plenty of merit and they could prove useful if, for example, access to the DOM is not possible.
All of these techniques will work. Choosing the right one depends on your use case. The first technique uses the actual grid-gap
property to create the gaps, but the others are perhaps easier to understand at a glance… and perhaps easier to maintain as well.
I have been struggling with this matter literally until the minute before thunderbird notified me about your post.
I have been trying to solve the issue with %-tuned background-image gradient stops, but my design is simpler (just columns) so I’m reading your article with pleasure.
Thanks!
“This solution doesn’t necessarily create a “real” distance between cells…”
Shouldn’t that be ‘border’ instead of ‘distance’?
Hi Gust, “this solution” in technique #2 refers to grid-gap not being used as a ‘spacer’ (between columns).
It uses grid-gap to mimic a 1px border-line, so this solution doesn’t really use the grid-gap property in it’s original form.
Technique 2 seems to be the cleanest of them. Thank you for sharing these tips!
Two notes related to this:
First, the article says grid is better than flexible boxes because it provides the
grid-gap
property. Note thatgrid-gap
got renamed to justgap
in order to also apply to Flexbox and multi-column layout. (So far, Firefox is the only browser that supports it in Flexbox layout yet, though.)Second, there already some discussion happening on the CSS Working group issue tracker to introduce a proper way to style the gutters between the cells, see https://github.com/w3c/csswg-drafts/issues/2748.
I saw it on Reddit and I did my own version. Very cool approach.
Why not box shadow, instead of borders? Maybe there is a particular reason I can’t see, but box-shadow seems simpler, you can use the same rule for all blocks and shadows are drawn on top of each other, they won’t take extra space or show a double line.