Newspaper design with CSS grid and border lines
Recently I had to craft a newspaper-like design that featured multiple row,- and column spans with divider-lines in between. If you’ve been around for a while, then perhaps you know that this could be a potential nightmare to code with regular CSS.
EDIT: This article has been published on CSS-Tricks.com! You can read the redacted version @ https://css-tricks.com/techniques-for-a-newspaper-layout-with-css-grid-and-border-lines-between-elements/
Business 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; old meets new
The reason newpaper-layouts could cause headaches is that everyday CSS is one-dimensional, meaning that content-cells are distributed on either a horizontal,- or on a vertical plane.
One would want properties good ol’ html-tables once provided: row,- and colspan’s to stretch cells in all directions. One would also want the benefits of modern day css, with all it’s responsiveness and flexible boxes that can grow to fill available spaces.
CSS grid combines the best of tables with the best of flex-boxes. Even better even, since grid provides a nifty feature for creating gutters between cells called “grid-gap”. Powerful as this may be, how does one create divider-lines exactly in the middle of those gutters?
This article describes three techniques how to create border-lines that are exactly in the middle of the gaps between content-cells.
#1: The ‘faux’ column technique
This solution creates ‘faux’ columns for creating vertical lines, and placing a grid on top of that. Horizontal dividers are painted if needed. N.B. ‘faux’ columns are created by using pseudo selectors in the grid-container.
HTML
<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 fp-cell--border-top">
<div class="fp-item">4</div>
</div>
<div class="fp-cell fp-cell--5 fp-cell--border-top">
<div class="fp-item">5</div>
</div>
<div class="fp-cell fp-cell--6 fp-cell--border-top">
<div class="fp-item">6</div>
</div>
<div class="fp-cell fp-cell--7 fp-cell--border-top">
<div class="fp-item">7</div>
</div>
</div>
CSS
$grijs_03: #efefef;
$grijs_15: #DADCE0;
.frontpage {
position: relative;
display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr;
grid-column-gap: 32px;
border-top: 1px solid $grijs_15;
border-bottom: 1px solid $grijs_15;
overflow: hidden;
&:before,
&:after {
position: absolute;
top: 0;
height: 100%;
content: '';
}
&:before {
right: 50%;
width: calc(25% + 8px);
border-left: 1px solid $grijs_15;
border-right: 1px solid $grijs_15;
}
&:after {
right: 0;
width: calc(25% - 8px);
border-left: 1px solid $grijs_15;
}
}
.fp-cell {
position: relative;
z-index: 2;
padding: 16px 0;
background-color: #fff;
}
.fp-cell--border-top {
&:before {
content: '';
position: absolute;
top: 0;
left: -16px;
right: -16px;
border-top: 1px solid $grijs_15;
}
}
.fp-cell--1 {
grid-row: 1 / span 3;
}
.fp-cell--2 {
grid-row: 1 / span 2;
grid-column: 2 / span 2;
}
.fp-item {
background-color: $grijs_03;
display: flex;
align-items: center;
justify-content: center;
min-height: 200px;
height: 100%;
}
Result
Setting up the lines between the columns
Create a four-columned container using display: grid and :before and :after pseudo-selectors to create 2 columns of 100% height of the container.
HTML:
<div class="frontpage"></div>
CSS:
$grijs_15: #DADCE0;
.frontpage {
position: relative;
display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr;
grid-column-gap: 32px;
border-top: 1px solid $grijs_15;
border-bottom: 1px solid $grijs_15;
overflow: hidden;
&:before,
&:after {
position: absolute;
top: 0;
height: 100%;
content: '';
}
&:before {
right: 50%;
width: calc(25% + 8px);
border-left: 1px solid $grijs_15;
border-right: 1px solid $grijs_15;
}
&:after {
right: 0;
width: calc(25% - 8px);
border-left: 1px solid $grijs_15;
}
}
Note: 25% of the container doesn’t take out gutter-width into account, so you have to compensate. This is calculated as: 25% minus (amount of gutters * gutter-width) / (amount of gutters * amount of columns). (3 * 48 / 3 * 4 = 12).
Create the grid
The design consists of 7 blocks of content. Place them in the container & give them a modifier class for future reference. Also make sure their z-index is higher than the the pseudo-selectors of the grid.
HMTL:
<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 class="fp-cell fp-cell--5"></div>
<div class="fp-cell fp-cell--6"></div>
<div class="fp-cell fp-cell--7"></div>
</div>
Set the background color for the cells (“fp-cell”) to white. This way the vertical lines won’t show through. Also set the vertical padding for the cell to 16px, in order to match half of the gutter.
Give the first and second content-blocks their unique span characteristics as shown in the design.
CSS:
.fp-cell {
position: relative;
z-index: 2;
padding: 16px 0;
background-color: #fff;
}.fp-cell--1 {
grid-row: 1 / span 3;
}
.fp-cell--2 {
grid-row: 1 / span 2;
grid-column: 2 / span 2;
}
Vertical divider-lines
If you look at the design, only the cells > 4 need a horizontal border. Give ’em a sweet modifier class.
HTML
<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 fp-cell--border-top"></div>
<div class="fp-cell fp-cell--5 fp-cell--border-top"></div>
<div class="fp-cell fp-cell--6 fp-cell--border-top"></div>
<div class="fp-cell fp-cell--7 fp-cell--border-top"></div>
</div>
CSS
.fp-cell--border-top {
&:before {
content: '';
position: absolute;
top: 0;
left: -16px;
right: -16px;
border-top: 1px solid $grijs_15;
}
}
(The negative margins are half of the gutter-width).
#2 The background-color technique
Another way to create dividers is to utilise grid-gap itself. This solution doesn’t necessarily create ‘real’ distance between cells, but rather uses grid-gap to leave some blank space where the background-color of the grid can shine through. Gutter-width is delegated to padding within the grid-cells.
HTML
<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 class="fp-cell fp-cell--5">
<div class="fp-item">5</div>
</div>
<div class="fp-cell fp-cell--6">
<div class="fp-item">6</div>
</div>
<div class="fp-cell fp-cell--7">
<div class="fp-item">7</div>
</div>
</div>
</div>
CSS
$grijs_03: #efefef;
$grijs_15: #DADCE0;.container {
overflow-x: hidden;
border-top: 1px solid $grijs_15;
border-bottom: 1px solid $grijs_15;
}
.frontpage {
position: relative;
display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr;
grid-gap: 1px;
margin: 0 -16px;
background-color: $grijs_15;
}
.fp-cell {
padding: 16px;
background-color: #fff;
}
.fp-cell--1 {
grid-row: 1 / span 3;
}
.fp-cell--2 {
grid-row: 1 / span 2;
grid-column: 2 / span 2;
}
.fp-item {
background-color: $grijs_03;
display: flex;
align-items: center;
justify-content: center;
min-height: 200px;
height: 100%;
}
Create a container for the grid
Since all cells have an extra 16px horizontal padding, the grid needs to be offset just as much. A wrapper container will take care of the overflow.
HTML
<div class="container">
<div class="frontpage">
...
</div>
</div>
CSS
.container {
border-top: 1px solid $grijs_15;
border-bottom: 1px solid $grijs_15;
overflow-x: hidden;
}.frontpage {
position: relative;
display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr;
grid-gap: 1px;
background-color: $grijs_15;
margin: 0 -16px;
}
#3 The cell-border technique
Solution three focusses on appending each cell with a border-right & border-bottom. Grid-gap is (like technique #2) mimicked by adding padding to the cell-content, which also need to be wrapped in an extra container.
HTML
<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 class="fp-cell fp-cell--5">
<div class="fp-item">5</div>
</div>
<div class="fp-cell fp-cell--6">
<div class="fp-item">6</div>
</div>
<div class="fp-cell fp-cell--7">
<div class="fp-item">7</div>
</div>
</div>
</div>
CSS
$grijs_03: #efefef;
$grijs_15: #DADCE0;
.container {
border-top: 1px solid $grijs_15;
overflow-x: hidden;
}
.frontpage {
margin: 0 -17px 0 -16px;
position: relative;
display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr;
}
.fp-cell {
padding: 16px;
background-color: #fff;
border-right: 1px solid $grijs_15;
border-bottom: 1px solid $grijs_15;
}
.fp-cell--1 {
grid-row: 1 / span 3;
}
.fp-cell--2 {
grid-row: 1 / span 2;
grid-column: 2 / span 2;
}
.fp-item {
background-color: $grijs_03;
display: flex;
align-items: center;
justify-content: center;
min-height: 200px;
height: 100%;
}
Asymmetrical negative margin
Each cell is given a border on the right & 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.
CSS
.frontpage {
margin: 0 -17px 0 -16px;
position: relative;
display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr;
}
Conclusion
Ockam’s razor stipulates that the simplest solution wins, which in our case would be technique #2. But then again, there is also merit in the other solutions (if only for the sake of playing around).
All techniques will work. Choosing one is dependant of your use-case. Technique 1 uses grid-gap as an actual gap, whereas techniques 2 and 3 are more easy to put your head around & easier to maintain.
Happy coding!