WebDev4R: CSS Grid

WebDev
There are two major systems in CSS for aligning things. These are flexbox and grid. Both of them are really useful to know. In this blog post, I’ll show you how grid works.
Author

Albert Rapp

Published

April 4, 2024

There are two major systems to align things in CSS. These are flexbox and grid layout. Both of them are really useful to know to have any chance to make nice websites or app. And today we’re going to focus on flexbox. And as always, if you want to see the video version of this blog post, you can find it on my YouTube channel:

What is grid?

As the name suggests, grid is a system to align things in a grid-like manner. This means that it’s a two-dimensional system, meaning that you can align things both horizontally and vertically. As we have seen in the previous blog post, flexbox is a one-dimensional system, meaning that you can only align things in one direction but with flex-wrap you can get similar results as with grid. Here’s how that looked last week:

library(htmltools)
div(
  style = css(
    display = 'flex', 
    flex_wrap = 'wrap',
    justify_content = 'space-around', 
    width = '300px',
    height = '300px',
    background = 'lightgrey'
  ),
  div(
    style = css(
      width = '100px',
      height = '100px',
      border = '1px solid #333333',
      background = 'red'
    )
  ),
  div(
    style = css(
      width = '100px',
      height = '100px',
      border = '1px solid #333333',
      background = 'red'
    )
  ),
  div(
    style = css(
      width = '100px',
      height = '100px',
      border = '1px solid #333333',
      background = 'red'
    )
  ),
  div(
    style = css(
      width = '100px',
      height = '100px',
      border = '1px solid #333333',
      background = 'red'
    )
  )
) |> 
  div(style = 'all: initial', br()) |> 
  browsable()

But with grid these kinds of use cases are much easier to handle. All you have to do is to replace the flexbox display with the grid display.

div(
  style = css(
    display = 'grid',  ## Change here
    width = '300px',
    height = '300px',
    background = 'lightgrey'
  ),
  div(
    style = css(
      width = '100px',
      height = '100px',
      border = '1px solid #333333',
      background = 'red'
    )
  ),
  div(
    style = css(
      width = '100px',
      height = '100px',
      border = '1px solid #333333',
      background = 'red'
    )
  ),
  div(
    style = css(
      width = '100px',
      height = '100px',
      border = '1px solid #333333',
      background = 'red'
    )
  ),
  div(
    style = css(
      width = '100px',
      height = '100px',
      border = '1px solid #333333',
      background = 'red',
      flex_shrink = 0
    )
  )
) |> 
  div(style = 'all: initial', br()) |> 
  browsable()

Well, that was a bust. There’s one more thing that you have to specify and that is the grid-template-columns and grid-template-rows. First, let’s have a look at the grid-template-columns.

div(
  style = css(
    display = 'grid', 
    grid_template_columns = '100px 100px',  ## Change here
    grid_template_rows = '100px 100px',  ## Change here
    width = '300px',
    height = '300px',
    background = 'lightgrey'
  ),
  div(
    style = css(
      border = '1px solid #333333',
      background = 'red'
    )
  ),
  div(
    style = css(
      border = '1px solid #333333',
      background = 'red'
    )
  ),
  div(
    style = css(
      border = '1px solid #333333',
      background = 'red'
    )
  ),
  div(
    style = css(
      border = '1px solid #333333',
      background = 'red'
    )
  )
) |> 
  div(style = 'all: initial', br()) |> 
  browsable()

Notice how I have removed the individual width and height properties from the squares. That’s because the grid-template-columns and grid-template-rows properties specify the width and height of the columns and rows. All the containers that are inside the grid are automatically adjusted to these dimensions.

So here we have specified that we want to have two columns with a width of 100px each. We could also use a relative unit that is based on the size of the parent container. For example, we could use fr which stands for “fraction of the available space”.

div(
  style = css(
    display = 'grid', 
    grid_template_columns = '1fr 2fr',  ## Change here
    grid_template_rows = '1fr 1fr',  ## Change here
    width = '300px',
    height = '300px',
    background = 'lightgrey'
  ),
  div(
    style = css(
      border = '1px solid #333333',
      background = 'red'
    )
  ),
  div(
    style = css(
      border = '1px solid #333333',
      background = 'red'
    )
  ),
  div(
    style = css(
      border = '1px solid #333333',
      background = 'red'
    )
  ),
  div(
    style = css(
      border = '1px solid #333333',
      background = 'red'
    )
  )
) |> 
  div(style = 'all: initial', br()) |> 
  browsable()

Well look at that our grey square is completely filled out now. And the second column in our grid is twice as large as the first one. That’s because inside grid-template-columns we have used 1fr 2fr. This means that the first column gets one fraction of the available space and the second column gets two fractions of the available space. Similarly, the rows are equally sized because we have used 1fr 1fr inside grid-template-rows.

Gap

Usually grids look nicer if there is a bit of space between the content in rows and columns. So let’s specify the column-gap and row-gap as well.

div(
  style = css(
    display = 'grid', 
    grid_template_columns = '1fr 2fr', 
    grid_template_rows = '1fr 1fr',
    column_gap = '10px',  ## Change here
    row_gap = '10px',  ## Change here
    width = '300px',
    height = '300px',
    background = 'lightgrey'
  ),
  div(
    style = css(
      border = '1px solid #333333',
      background = 'red'
    )
  ),
  div(
    style = css(
      border = '1px solid #333333',
      background = 'red'
    )
  ),
  div(
    style = css(
      border = '1px solid #333333',
      background = 'red'
    )
  ),
  div(
    style = css(
      border = '1px solid #333333',
      background = 'red'
    )
  )
) |> 
  div(style = 'all: initial', br()) |> 
  browsable()

We could also add a padding between the outer borders of the grid and the parent container.

div(
  style = css(
    display = 'grid', 
    grid_template_columns = '1fr 2fr', 
    grid_template_rows = '1fr 1fr',
    padding = '10px',  ## Change here
    row_gap = '10px',
    column_gap = '10px',
    width = '300px',
    height = '300px',
    background = 'lightgrey'
  ),
  div(
    style = css(
      border = '1px solid #333333',
      background = 'red'
    )
  ),
  div(
    style = css(
      border = '1px solid #333333',
      background = 'red'
    )
  ),
  div(
    style = css(
      border = '1px solid #333333',
      background = 'red'
    )
  ),
  div(
    style = css(
      border = '1px solid #333333',
      background = 'red'
    )
  )
) |> 
  div(style = 'all: initial', br()) |> 
  browsable()

And for more complicated layouts you can also use the grid-template-areas property. But don’t forget to set the grid-area property for each container inside the grid.

div(
  style = css(
    display = 'grid', 
    width = '300px',
    height = '300px',
    padding = '10px',  ## Change here
    row_gap = '10px',
    column_gap = '10px',
    background = 'lightgrey',
    grid_template_areas = '
    "a a ." 
    "b c ." 
     ". d  d"'  ## Change here
  ),
  div(
    style = css(
      grid_area = 'a',
      border = '1px solid #333333',
      background = 'red'
    )
  ),
  div(
    style = css(
      grid_area = 'b',
      border = '1px solid #333333',
      background = 'red'
    )
  ),
  div(
    style = css(
      grid_area = 'c',
      border = '1px solid #333333',
      background = 'red'
    )
  ),
  div(
    style = css(
      grid_area = 'd',
      border = '1px solid #333333',
      background = 'red'
    )
  )
) |> 
  div(style = 'all: initial', br()) |> 
  browsable()

Use grid with {bslib}

The bslib makes it pretty easy to create cards (nothing but containers) and align them in a grid-like manner. Currently, you can only make all cards use the same width.

library(bslib)

layout_column_wrap(
  card('Card 1'),
  card('Card 2'),
  card('Card 3'),
  card('Card 4')
)
Card 1
Card 2
Card 3
Card 4

But by injecting a bit of custom CSS, you can make the cards use different widths. All of that is based on the grid layout.

layout_column_wrap(
  card('Card 1'),
  card('Card 2'),
  card('Card 3'),
  card('Card 4'),
  style = 'grid-template-columns: 1fr 2fr;'
)
Card 1
Card 2
Card 3
Card 4

Conclusion

There’s a whole lot more you can learn about grid but for now this should give you a good start. Have a great day and see you next time. And if you found this helpful, here are some other ways I can help you:


Stay in touch

If you enjoyed this post, then you may also like my weekly 3-minute newsletter. Every week, I share insights on data visualization, statistics and Shiny web app development. Reading time: 3 minutes or less. You can check it out via this link.

You can also support my work with a coffee