🏠 Grid

By Robert Laing

I’ve found CSS’s various layout models extremely confusing, and had to figure out the basics of CSS grid layout through trial and error. An initial mistake was to overcomplicate things until I realized auto-placement generally does what you want and you rarely need grid-template-areas.

A 3x3 Tic-Tac-Toe grid

As explained in my HTML elements article, web pages follow a tree model where content such as images and text are leaves which are placed into containers such as div or section-related elements.

A grid needs a container (aka wrapper) defined either display: grid or display: inline-grid. Only the direct children of this container are seen as cells, something that tripped me up initially.

The HTML below follows the best practice of Visual order on the page follows DOM order.

I initially gave every grid cell its own CSS block stipulating its grid-column and grid-row. With more experience I realized if I simply set the number of columns, the tracks — ie rows if working with columns — would take care of themselves.

I like to follow the convention of labeling my columns alphabetically and my rows numerically, so the ids for my nine cells would look like so:

<main class="tableau">
  <div id="a1">a1</div>
  <div id="b1">b1</div>
  <div id="c1">c1</div>
  <div id="a2">a2</div>
  <div id="b2">b2</div>
  <div id="c2">c2</div>
  <div id="a3">a3</div>
  <div id="b3">b3</div>
  <div id="c3">c3</div>
</main>

I like to use tableau as my wrapper class name since it’s mainly for card and other games.

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

Firefox’s inspector tool helps show what’s going on.

This is all we need to get 3 equally spaced colums with 3 rows.

Sizing Cells

The key thing to avoid is horizontal scrolling irrespective of whether the user is seeing a page as portrait on a smartphone or landscape on a laptop, so we need to ensure the overall container gets pushed bigger than the screen width.

Here the relatively new orientation CSS media feature along with the var help me make the most of whatever screen real estate my site appears on with square cells:

@media (orientation: landscape) {
  :root {
    --side: 30vh;
  }
}

@media (orientation: portrait) {
  :root {
    --side: 30vw;
  }
}

.tableau {
  display: grid;
  grid-template-columns: repeat(3, var(--side));
  grid-auto-rows: var(--side);
}

.tableau > div {
  border: 1px solid #999999;
  border-radius: 3px;
}
Now I have squares, but I haven’t got things centred.

Columns or rows?

The above combination of grid-template-columns: track-list to set column widths with grid-auto-rows: height to set row heights is usually what I want.

An exception is Bowling Solitaire where to get the pyramid of pins I wanted to work by rows and leave columns flexible so used grid-template-rows with the cards in each row centred in a shared column.

Since most of my learning projects have involved solitaire card games, has been a natural choice. Starting out, I overcomplicated my stylesheets by including things which I subsequently learnt are best done automatically.

As explained in transposing, if I want to work by row instead of column, I use grid-auto-flow: row;.

Centering the container

I’ve personally found the difference between justify-content, justify-items, and justify-self hard to understand.

Through trial and error, I found adding justify-content: center; to the .tableau {} stanza did the job of horizontally centering the 3x3 grid.

Horizontal Alignment

Note: auto track sizes (and only auto track sizes) can be stretched by the align-content and justify-content properties. Therefore, by default, an auto-sized track will take up any remaining space in the grid container. — MDN

Not having read the above warning, I didn’t know that grid-template-columns switched off justify-content.

Eventually I figured out text-align did the job. The name is confusing because it doesn’t just align text, but any inline content (buttons, images…) in the cell.

Adding text-align: center; to either .tableau {} or .tableau > div {} got me this:

All centred horizontally.

Vertical Alignment

Since we’re using grid-auto-rows, align-content works here.

If the grid was done with auto aligned columns instead of rows, then vertical-align would be used the same way as text-align above.

.tableau {
  display: grid;
  grid-template-columns: repeat(3, var(--side));
  grid-auto-rows: var(--side);
  justify-content: center;
}

.tableau > div {
  border: 1px solid #999999;
  border-radius: 3px;
  text-align: center;
  align-content: center;
}
align-content works on the text vertically, but justify-content doesn’t work on text for some reason.

I thought that moving align-content: center; to its parent container tableau as align-items: center; would work the same, but it did nothing. I have no idea what align-items does in grid layouts. Possibly it’s just meant for flex.

Mind the gap

Using the gap property in the container block:

.tableau {
  display: grid;
  grid-template-columns: repeat(3, var(--side));
  grid-auto-rows: var(--side);
  place-content: center;
  gap: 4px;
}

The cells get separated:

gap: 4px

Transposing the Matrix

To flip rows into columns, use grid-auto-flow: row;.

.tableau {
  display: grid;
  grid-template-columns: repeat(3, var(--side));
  grid-auto-flow: row;
  place-content: center;
  gap: 4px;
}
I initially thought grid-template-rows would do this, but that was a bad idea.

Individual Cell Alignment

To make only the “a1” text to in the topl left corner, the values set in .tableau > div {...} get overwritten like so:

#a1 {
  text-align: start;
  align-content: start;
}
a1 styled individually

I experimented with align-items and align-self at various levels of the hierarchy, but couldn’t get them to work.

Unequal Sized Cells

This is based on an example in Mozilla’s Basic concepts of grid layout article.

For games, I prefer not doing this style of layout since I want every grid cell to be a unique drag or drop target for images. I also prefer auto placement since then the order of elements in the HTML file tallies with the grid.

This example shows the cells can be placed on top of each other using grid-column-start and relatives.

Layered boxes
@media (orientation: landscape) {
  :root {
    --side: 30vh;
  }
}

@media (orientation: portrait) {
  :root {
    --side: 30vw;
  }
}

.tableau {
  display: grid;
  grid-template-columns: repeat(3, var(--side));
  place-content: center;
  gap: 4px;
}

.tableau > div {
  aspect-ratio: 1 / 1;
}

#big {
  grid-column-start: 1;
  grid-column-end: 4;
  grid-row-start: 1;
  grid-row-end: 4;
  background-color: red;
}

#medium {
  grid-column-start: 2;
  grid-column-end: 4;
  grid-row-start: 2;
  grid-row-end: 4;
  background-color: green;
}

#small {
  grid-column-start: 3;
  grid-column-end: 4;
  grid-row-start: 3;
  grid-row-end: 4;
  background-color: blue;
}

Layered Cells

On my first attempts to use z-index, I got nowhere until I discovered the default position: static causes it to be ignored.

This isn’t the case for grid cells. If I edit the above to give the big block z-index: 1;, it hides the medium and small blocks which have the default z-index: 0; value. If I change the medium block to z-index: 1;, it hides the smaller block.

Automatic Sizing

By default, grid cells expand to the size of their content.

If a cell has a bigger image placed in it that its size, it expands. Something that tripped me up creating these solitaire card games was I wanted overlapping card images in columns, eg The Bogey vertically or Skyway horizontally. Initially I placed blank images in the div containers, thinking position: relative was what I wanted to move an image within its container. Here I hit the snag that the grid cells size themselves according to the blank images position before it got moved, so I had to use position: absolute which causes the grid layout to pretend the images aren’t there.