Techniques for a Newspaper Layout with CSS Grid and Border Lines Between Elements

Avatar of Marco Troost
Marco Troost on

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.

Newspaper design with line dividers between cells

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.

Column and row spans in a CSS grid layout

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.