🏠 Grid

By Robert Laing

Columns or rows?

The key reason to work by columns instead of rows is to avoid horizontal scrolling.

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

My first mistake was thinking I needed to specify both grid-template-columns and grid-template-rows.

Perhaps if you come from a culture with a vertical writing system, working by rows might seem natural. But to me, left to right is an obvious choice, so all I want is columns. The CSS grid module is cluttered with stuff like grid-template-areas which besides overcomplicating things, lead to not following 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.

In CSS grid parlance, this is called auto-placement.

A 3x3 Tic-Tac-Toe grid

Let’s start with the HTML. 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 name since it’s mainly for card and other games.

Containers aren’t content

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.

.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 Containers

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.

Sizing Cells

My next step is to make all the cells in my grid squares. 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:

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

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

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

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

Row Height

Instead of aspect-ratio: 1 / 1;, I could use grid-auto-rows: length set to the same length as each cell’s width:

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

Horizontal Alignment

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.

Centering the “content” as in text of the individual cells turned out to be tricky. Rather than justify-, using text-align by adding text-align: center; to either .tableau {} or .tableau > div {} got me this:

All centred horizontally.

Vertical Alignment

This is done with align-content, align-items, and align-self, not to be confused with text-align which works horizontally while vertical-align is for vertical text movement.

If, as in this case, I want to use both justify-content: center; and align-content: center;, there’s the shorthand place-content: center;.

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

.tableau > div {
  border: 1px solid #999999;
  border-radius: 3px;
  aspect-ratio: 1 / 1;
  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));
  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.

https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Grid_layout/Basic_concepts#fixed_and_flexible_track_sizes

fit-content(length) function on the container or vice-versa

With CSS, the template boxes expand to allow any amount of waffle by default, both a bug and a feature.