CSS Grid: Typing a responsive 20-line magazine layout


Recently, I worked on a modern implementation of a blog roll (a list of external useful / interesting blogs). The idea was to provide readers with a selection of the latest posts on these blogs, packaged in a magazine layout, rather than a dry list of links in the sidebar.

The easiest part of the task is to get a list of posts and their excerpts (excerpt - introductory text before the kat) from our favorite RSS feeds. To do this, we used the Feedzy lite WordPress plugin , which can aggregate several feeds into one list, sorted by time - the ideal solution in our case. The difficult part is to make everything beautiful.

The plugin’s standard list interface is probably tasteless, so I wanted to style it as a newspaper or magazine’s website with a mixture of large and small “selected” blocks.

Sounds like the perfect occasion to use CSS Grid! Create a Grid Layout for different layouts, say, one five-column and one three-column, and then switch between them using media queries on different screen sizes. Right? But do we really need these media queries, and all this trouble with defining control points, if you can just use the Grid parameter auto-fit, which will make the adaptive grid for us?

This idea seemed tempting to me, but when I started adding elements spanning several columns of the grid ( spans), the grid began to creep out of the page on narrow screens. Media queries seemed like the only solution. But I found something better!

Having studied a number of articles on CSS Grid, I found that they are mainly divided into two types:

  1. How to create an interesting layout with spans, but with a given number of columns.
  2. How to create an adaptive layout on a Grid, but with columns of the same width (i.e. without spans).

I want the grid to implement both this and that: a fully adaptive layout using adaptively resizing multi-column elements.

The beauty is that as soon as you begin to understand the limitations of adaptive grids and why and when spans break adaptability, it’s easy to create a magazine layout of several tens of lines of code and one media query (or without them at all, if you want to limit the variety of spans).

Here is a clear comparison of the RSS plugin from the box and the result of our work (clickable):


This is a fully adaptive magazine layout with colored “selected” blocks that dynamically adapt to the layout depending on the number of columns. The page displays about 50 posts, but the layout code does not depend on the number of elements. You can easily increase the number of posts to 100 in the plugin settings, and the layout will remain interesting to the very bottom.

All this is achieved exclusively through CSS and using only one media query to display content in one column on the narrowest screens (less than 460 pixels).

What is most incredible, the entire layout took only 21 lines of CSS (not counting the general styles of the site). However, in order to achieve such flexibility using so little code, I had to dive deep into the darkest depths of the CSS Grid and learn how to get around some of its limitations.

The code on which the entire layout rests is incredibly short, and all thanks to the splendor of the CSS Grid:

.archive {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
  grid-gap: 32px;
  grid-auto-flow: dense;
}

/*    */
.article:nth-child(31n + 1) {
  grid-column: 1 / -1;
}
.article:nth-child(16n + 2) {
  grid-column: -3 / -1;
}
.article:nth-child(16n + 10) {
  grid-column: 1 / -2;
}

/*     */
@media (max-width: 459px) {
  .archive {
    display: flex;
    flex-direction: column;
  }
}

The technique described in this article can be safely used to stylize any dynamically generated content, whether it is the output of the widget of recent posts, archive pages or search results.

Create adaptive mesh


I created 17 elements to showcase the diversity of future content - headlines, pictures, and excerpts, and wrapped in <div></div>

<div class="archive">
  <article class="article">
    <!--  -->
  </article>

  <!--  16  -->

</div>

The code for turning these elements into an adaptive grid is especially compact:

.archive {
  /*     - */
  display: grid;
  /*        ,       180 . */
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  /*     */
  grid-gap: 1em;
}

→ Demo on CodePen

Note how the line height automatically adjusts to the highest block of content in the line. If you change the width in the demo from the link above, you will see that the elements increase and decrease automatically and the number of columns changes from 1 to 5, respectively.

Here in action, we see the CSS Grid magic calledauto-fit. This keyword works in conjunction with the functionminmax()applied togrid-template-columns.

How it works


The five-column layout itself can be obtained like this:

.archive {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
}

However, this creates a five-column layout that stretches and contracts at different screen widths, but always remains five-column, which leads to terribly narrow columns on small screens. The first thing that comes to mind is to write a bunch of media queries and redefine a grid with a different number of columns. That would work, but the keyword auto-fitdoes it all automatically.

To auto-fituse the function as we need minmax(). Thus, we kind of tell the browser how much you can compress the columns, and how much to stretch. When one of the boundaries is reached, the number of columns increases or decreases, respectively.

.archive {
  grid-template-columns: repeat (auto-fit, minmax(180px, 1fr));
}

In this example, the browser will try to accommodate as many columns as wide as 180 pixels wide. If there is excess space, all columns will expand, dividing it among themselves equally. This is what dictates the meaning 1fr: to make the column sizes equal fractions ( fr actions) of the available width.

If you stretch the browser window, all the columns will grow equally with increasing free space. As soon as the newfound space reaches 180 pixels, a new column appears in its place. And if you reduce the browser window, everything happens the other way around, perfectly fitting the grid until it turns into a single-column layout. Magic!

→ Video demo

And all this adaptability thanks to one line of code. Well cool?

Creating spans with “autoflow: dense”


So, at the moment we already have an adaptive grid, that's just all of its elements - the same width. The layout of a newspaper or magazine implies the presence of selected blocks, in this context, those that would cover two, three, or even all available columns.

To create multi-column spans, we can use the property grid-column: spanin those elements that should occupy more space. Suppose we want the third list item to be two columns wide:

.article:nth-child(3) {
  grid-column: span 2;
}

However, after adding spans, a lot of problems may appear. Firstly, holes can form in the grid in those cases when the wide element does not fit on its line and auto-fittransfers it to the following:


This is easily fixed by adding a property grid-auto-flow: denseto the grid. Due to this property, the browser understands that holes need to be filled with other elements. This creates a flow around the wider elements narrower:


Please note: the order of the elements is broken, now the fourth element is in front of the third. As far as I know, this cannot be bypassed, this is one of the limitations of CSS Grid that must be accepted.

Ways to identify spans


There are several ways to specify the number of columns that an item should occupy. It is easiest to apply grid-columns: span [n]to one of the elements, where nis the number of columns that the element will occupy. The third element in our layout has a property registered grid-column: span 2, which explains why its width is twice as large as other elements.

To use other methods, you must specify the grid lines . The grid lines are numbered as follows:


Grid lines can be indicated from left to right with positive numbers (for example, 1, 2, 3), or from right to left with negative numbers (-1, -2, -3). They can be used to place elements in the grid using a property grid-column, like this:

.grid-item {
  grid-column: ( ) / ( );
}

So, the grid lines expand our ability to define spans. Flexibility is added by the ability to replace the initial or final value with a keyword span. For example, the three-columned blue block in the example above can be created by applying any of these properties to the eighth grid element:

  • grid-column: 3 / 6
  • grid-column: -4 / -1
  • grid-column: 3 / span 3
  • grid-column: -4 / span 3
  • grid-column: span 3 / -1
  • etc.

In a non-adaptive grid (i.e., with a fixed number of columns), each of these properties gives the same result (as in the example with the blue block above). But if the grid is adaptive and the number of columns changes, the difference becomes very noticeable. Some spans break the layout with auto-wrap turned on, which makes it seem that these two solutions are incompatible. Fortunately, some tricks will allow us to combine them safely.

But first, we need to understand the problem.

Horizontal Scrolling Issues


Here are a few “featured items” created using the grid line method (clickable):


Over the entire width (five columns), everything looks good, but if you reduce the screen to a size at which there should be two columns, the layout breaks this way:


As you can see, our grid has lost its adaptability and, although the container has shrunk, the grid is trying to support all five columns. To do this, she continues to try the same column width and eventually goes beyond the boundaries of her container to the right. From this, horizontal scrolling appears.

Why it happens? The problem is that the browser is trying to comply with our exact directions for the grid lines. At this width, the auto-fitgrid should only display two columns, but our stack line numbering system contradicts this, referring specifically to the fifth line. This contradiction leads to disorder. To correctly display our implied two-column grid, only numbers 1, 2, 3 and -3, -2, -1 can be used, like this:


But if one of the elements of our grid contains instructions grid-columnoutside these limits, say 4, 5 or -6, the browser receives ambiguous instructions. On the one hand, we ask you to automatically create flexible columns (which should - implicitly - remain two at this screen width). On the other hand, we explicitly referred to grid lines, which cannot exist in a two-column format. When there is a contradiction between implicit (automatic) columns and their explicitly defined number, the grid always prefers an explicit definition . This is how unwanted columns and horizontal overflow appear (what they call CSS data loss) Spans, like grid line numbers, can create explicit column definitions. grid-column: span 3 (the eighth grid element in the demo) forces the grid to explicitly have at least three columns, despite the fact that we want two implicit ones.

It may seem that the only option is to use media queries to change the values grid-columnat the desired width ... but do not rush! I thought so too at first. But, after a little reflection and playing around with different settings, I found that there are some ways to get around this problem, thanks to which we still have only one media request for the narrowest devices.

Solutions


As it turned out, the trick is to determine spans using only line numbers that are available for the narrowest grid of those planned for display. In this case, we are talking about a two-column grid (recall, we use a media query for single-column display). Thus, you can safely use the numbers 1, 2, 3 and their negative pairs without breaking the grid.

At first I thought that I would limit myself to the width of the span in two columns using these combinations of numbers:

  • grid column: span 2
  • grid-column: 1 /3
  • grid-column: -3 / -1


Which remain perfectly adaptable up to two speakers:


Although this works , from a design point of view this is a serious limitation, and not too bright. I wanted to make spans in the width of three, four or even five columns on wide screens. But how? My first thought was to return to media queries again (omg, it’s hard to get rid of old habits), but still I tried to avoid this approach and look at responsive design from a different angle.

Looking again at the list of available numbers, I suddenly realized that positive and negative numbers in the initial and final values grid-columncan be combined, for example 1/-3,2/-2. It would seem nothing interesting. But it doesn’t seem like that anymore when you understand the position of the lines after changing the grid size: the spans change the width in accordance with the width of the screen. A whole bunch of new opportunities for adaptive spans opens, in particular, elements that cover a different number of columns with a change in the width of the screen, without any media queries.

The first example I discovered is grid-column: 1/-1. This property turns the element into a banner in full width, filling all columns from first to last, even when there is only one column!

Usinggrid-column: 1/-2, you can create a span "almost full width", which fills all the columns from left to right, except the last. In a two-column layout, such a span adaptively turns into an ordinary element in one column. Surprisingly, it works even when compressing the layout to one column. (It seems that the reason is that the grid will not reduce the element to zero width and therefore it remains the width of one column, as in the case with grid-column: 1/1.) I assumed that it grid-column: 2/-1should work the same way, only leave one column untouched on the left, and not on the right . It turned out to be almost right, when compressing the layout to one column, overflow still arises.

Then I tried a combination1/-3. It worked well on wide screens - filling at least three columns - and on narrow ones - filling only one. I thought that with a two-column grid something strange would turn out, because since the first line of the grid is the same as the line under the number -3. To my surprise, the item displays correctly in one column.

After numerous experiments, I found out that there are 11 suitable values grid-columnfrom those available in a two-column grid. Three of them work even in a single-column layout. Seven others work correctly up to two columns, and for proper display in one column they will need only one media query.

Here is the complete list:


Demonstration of adaptive grid-column values ​​on different screen sizes in an auto-fit grid. ( Demo )

In general, despite a rather limited subset of adaptive spans, there are many opportunities.

  • 2/-2 - An interesting combination, creates a centered span that works right down to the single-column grid!
  • 3/-1 - least useful as it leads to overflow even on two columns.
  • 3/-3 - a pleasant surprise.

Due to the variety of values grid-columnfrom this list, it is possible to create an interesting and fully responsive layout. Using a single media query for the narrowest single-column display, we can manage ten different patterns grid-column.

That same media query is quite simple, even let's say straightforward. In our example, he is responsible for switching the grid display to flexbox:

@media (max-width: 680px) {
  .archive {
    display: flex;
    flex-direction: column;
  }

  .article {
    margin-bottom: 2em;
  }
}

Here is the final grid, which, as you may have noticed, is fully responsive - from one to five columns (clickable):


Usage: nth-child () for repeating dynamic width


To reduce my code to two dozen lines, I applied another trick. The selector :nth-child(n)allowed me to style a large number of elements at once. My whole idea with spans was to be applied to many posts in the feed so that selected blocks would appear on the page regularly. First, I wrote a comma-separated list of selectors with a clearly defined element number:

.article:nth-child(2),
.article:nth-child(18),
.article:nth-child(34),
.article:nth-child(50)  {
  background-color: rgba(128,0,64,0.8);
  grid-column: -3 / -1;
}

In speed, I realized that this is a very time-consuming process, especially when you have to copy this entire list of conditions for each child element that needs to be changed inside the article - heading, links, etc. During prototyping, I was forced to manually change the numbers in each of these lists every time I wanted to play with the position of the spans. A boring and error-prone process.

It was then that I realized that you can take advantage of the cool feature of the pseudo-selector :nth-child: instead of an integer value, enter an expression, for example :nth-child(2n+ 2), which means every second child element.

This is how I used :nth-child([])to create a full-width blue block in my grid that appears at the top of the page and then halfway through the list:

.article:nth-child(31n + 1) {
  grid-column: 1 / -1;
  background: rgba(11, 111, 222, 0.5);
}

A piece of code in brackets ( 31n + 1) is responsible for choosing the 1st, 32nd, 63rd, etc. child element. The browser starts the loop starting with n = 0 ( 31 * 0 + 1 = 1), then n=1( 31 * 1 + 1 = 32), and finally n=2( 31 * 2 + 1 = 63). In the latter case, the browser understands that there is no 63rd child element, ignores the rule, stops the cycle and applies the rule to the 1st and 32nd elements.

I’m doing something similar for the purple blocks that appear on the left or on the right throughout the page:

.article:nth-child(16n + 2) {
  grid-column: -3 / -1;
  background: rgba(128, 0, 64, 0.8);
}

.article:nth-child(16n + 10) {
  grid-column: 1 / -2;
  background: rgba(128, 0, 64, 0.8);
}

The first selector is for the left purple blocks. The expression 16n + 2is responsible for applying styles to every 16th grid element, starting with the second.

The second selector is for the right purple blocks. The interval is the same ( 16n), but the shift is different ( 10). As a result, these blocks are regularly found on the right side of the grid, in elements numbered 10, 26, 42, etc.

For the visual styles of these elements, I used another trick to avoid code repeating. For common styles of purple blocks (for obvious background-color, for example), you can use one selector:

.article:nth-child(8n + 2) {
  background: rgba(128, 0, 64, 0.8);
  /* Other shared syling */
}

This selector will select items 2, 10, 18, 26, 34, 42, 50, and more. In other words, he selects both left and right blocks.

This works because 8n- this is exactly half 16n, and the shift difference in the two selectors is also 8.

Final word


CSS Grid can now be used to create flexible, responsive meshes with minimal code. However, if you avoid the use of retrograde media queries, there are significant restrictions on the positioning of elements in the grid.

It would be very cool to be able to create spans that would not lead to horizontal scrolling and overflow on small screens. Now we can tell the browser: “Make an adaptive grid, please,” and it does it very well. But just add: “Oh, and this element of the grid is stretched into four columns, please,” - and he waves the handle to narrow screens, preferring the request for a four-column span, rather than an adaptive grid. It would be possible to make the grid do the opposite, for example like this:

.article {
  grid-column: span 3, autofit;
}

Another issue with responsive grids is the last line. Changing the screen width can often cause it to be blank. I tried for a long time to find a way to stretch the last element of the grid to the remaining columns (respectively, fill the row), but it seems to be impossible. At least for now. It would be nice to be able to set the initial position of the element with a keyword, like auto, as it were, saying “Fill the line to the end, starting from the left edge”. Something like this:

.article {
  grid-column: auto, -1;
}

... that would stretch the span on the left edge of the grid to the end of the line.


All Articles