Edwin
Posted on February 2, 2020
Recently, I felt like creating a game as a nice little side project. The game involves dice, so I needed some way to visualise them. This article will explain how you can create dice using only HTML and CSS.
Flexing with CSS 💪
Why reinvent the wheel if other people have already solved the problem for us, right? A quick search around the web led me to this article by Landon Schropp, which describes how to make great looking dice using CSS flexbox.
He implemented the face of a die by simply positioning a span
element for each pip of the die inside of a div
. The div.column
contains the vertically aligned pips.
<div class="fourth-face">
<div class="column">
<span class="pip"></span>
<span class="pip"></span>
</div>
<div class="column">
<span class="pip"></span>
<span class="pip"></span>
</div>
</div>
These pips are positioned using flexbox and pushed to opposite sides of the die using the justify-content: space-between
property. His final solution requires quite a bit of CSS to correctly style every possible face value.
We can do better with CSS grid 🔥
While I was playing around with the code, I realised that the pips of a traditional die are aligned in three rows and three columns, which is a great opportunity to use CSS grid.
Imagine the face of a die as a 3x3 grid, where each cell represents the position of a pip:
+---+---+---+
| a | b | c |
+---+---+---+
| d | e | f |
+---+---+---+
| g | h | i |
+---+---+---+
CSS grid is supported by all evergreen browsers, but as you may expect, Internet Explorer offers only very basic support. The final result will therefore not work in IE11.
For a full guide on CSS grid, please refer to the amazing Grid by Example, by Rachel Andrews.
Creating the grid layout
To create a simple 3 by 3 grid using CSS, the only thing we need to do is set a container element to display: grid
and tell it that we want three equally sized rows and columns:
.face {
display: grid;
grid-template-rows: 1fr 1fr 1fr;
grid-template-columns: 1fr 1fr 1fr;
}
The fr
unit allows you to set the size of a row or column as a fraction of the free space of the grid container; in our case we want one third of the available space, so we use 1fr
three times.
Instead of writing 1fr 1fr 1fr
we can use repeat(3, 1fr)
to repeat the 1fr
unit three times. We also use the grid-template
shorthand property that defines rows / columns
:
.face {
display: grid;
grid-template: repeat(3, 1fr) / repeat(3, 1fr);
}
The only HTML that we need is a div.face
container with the above CSS and a span.pip
for each pip:
<div class="face">
<span class="pip"></span>
<span class="pip"></span>
<span class="pip"></span>
<span class="pip"></span>
</div>
The pips will be automatically placed in each of the cells, from left to right:
Positioning the pips
Now we get to the point where we need to position the pips for each of the dice values. It would be nice if the span
s automatically flowed to the correct positions in the grid for each value. Sadly, we will need to set the position of each of the pips individually.
Recall the ASCII table at the beginning of the article? We are going to create something very similar using CSS. Instead of labeling the cells in the row order, we use this specific order so we only need a minimal amount of CSS to fix the edge cases:
+---+---+---+
| a | | c |
+---+---+---+
| e | g | f |
+---+---+---+
| d | | b |
+---+---+---+
Two of the cells are left empty, because they are never used on our dice.
Grid template areas
We can translate this layout to CSS using the magical grid-template-areas
property (which replaces the grid-template
used above):
.face {
display: grid;
grid-template-areas:
"a . c"
"e g f"
"d . b";
}
So instead of using traditional units to size our rows and columns, we can just refer to each cell with a name. The syntax itself provides a visualization of the structure of the grid, just like our ASCII table. The names are defined by the grid-area
property of the grid item. The period in the middle column signifies an empty cell.
Placing pips in an area
We use the grid-area
property to give a name to this grid item. The grid template (above) can then reference the item by its name to place it in a specific area in the grid. The :nth-child()
pseudo selector allows us to target each pip individually:
.pip:nth-child(2) {
grid-area: b;
}
.pip:nth-child(3) {
grid-area: c;
}
.pip:nth-child(4) {
grid-area: d;
}
.pip:nth-child(5) {
grid-area: e;
}
.pip:nth-child(6) {
grid-area: f;
}
As you can see, the values 1, 3 and 5 are still incorrect. Because of the order of the grid-template-areas
that we chose earlier, we only need to reposition the last pip of each of these dice. To get the result that we want, we combine the :nth-child(odd)
and :last-child
pseudo selectors:
.pip:nth-child(odd):last-child {
grid-area: g;
}
Setting the position of each element individually does not scale well. But for our goal, the number of cases is very limited, it works for all dice values and it allows us to keep our HTML simple. It feels like a cleaner solution than the flexbox version above. It is also easier to translate into components using a JavaScript framework such as React as you will see below.
Final result 🎲
The implementation above only uses HTML and CSS, but I also created a very basic React app to show how you can use the dice as components.
Posted on February 2, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.