Celebrate CSS Grid support by re-creating the iOS Calculator

Robert Mion
Statuscode
Published in
11 min readMar 11, 2017

This is what it should* look like when we’re done.

Animation demonstrating final responsive layout for this project, the iOS Calculator

*In a Grid-supporting browser, that is. As of this writing, that includes Firefox 52 and Chrome 57.

TL;DR

Start with HTML

  • Wrapping div with four direct child elements: h1, three divs
  • Each div child contains several buttons

Code snippet

<div class=”wrapper”>
<h1>0</h1>
<div class=”digits flex”>
<button>9</button>
<button”>8</button>
<button>7</button>
<button”>6</button>
<button>5</button>
<button>4</button>
<button>3</button>
<button>2</button>
<button>1</button>
<button class=”wide”>0</button>
<button>.</button>
</div>
<div class=”modifiers subgrid”>
<button>AC</button>
<button>+/-</button>
<button>%</button>
</div>
<div class=”operations subgrid”>
<button>/</button>
<button>X</button>
<button>-</button>
<button>+</button>
<button>=</button>
</div>
</div>
View after adding HTML

Add all non-grid/flexbox/alignment-related CSS

Note the declaration involving ‘min-height: 100vh;’ on the outer ‘wrapper’ div. This will ensure the layout remains as tall as the viewport’s height.

Code snippet

html, body {
margin: 0;
padding: 0;
font-family: sans-serif;
}
.wrapper {
min-height: 100vh;
}
h1 {
background-color: #333;
color: white;
margin: 0;
padding: 1rem;
font-size: 4rem;
}
button {
font-size: 2rem;
border: 1px solid #333;
}
.modifiers button {
background-color: #ccc;
}
.operations button {
background-color: orange;
}
.digits button {
background-color: #efefef;
}
View after adding base CSS in place

First new thing: ‘display: grid;’

This turns an element into a Grid Container, making each direct child a Grid Item. It’s where the magic begins.

Code snippet

/* Applies to three elements */
.wrapper,
.subgrid {
display: grid;
}
<div class=”wrapper”>
<h1>0</h1>
<div class=”digits flex”>
<button>9</button>
<button”>8</button>
<button>7</button>
<button”>6</button>
<button>5</button>
<button>4</button>
<button>3</button>
<button>2</button>
<button>1</button>
<button class=”wide”>0</button>
<button>.</button>
</div>
<div class=”modifiers subgrid”>
<button>AC</button>
<button>+/-</button>
<button>%</button>
</div>
<div class=”operations subgrid”>
<button>/</button>
<button>X</button>
<button>-</button>
<button>+</button>
<button>=</button>
</div>
</div>
View after adding ‘display: grid’

First intimidating thing: ‘grid-template-areas’

Code snippet

.wrapper {
grid-template-areas:
"total total total total"
"modif modif modif oper"
"digit digit digit oper"
"digit digit digit oper"
"digit digit digit oper"
"digit digit digit oper";
}

This is what I hope you see

  • Six lines
  • Each line contains exactly four groups of letters, each separated by a space, and lines enclosed by quotation marks
  • Many lines share groups of letters (i.e. total, modif, digit, oper)

Technically speaking

Use the new property, ‘grid-template-areas’ to define an explicit grid into which grid items can be placed.

In layman’s terms

Each grid item must be placed somewhere within its grid container. Therefore, it will either go into an area you — the author — have ‘explicitly’ assigned it to, or will automatically be placed into an area ‘implicitly’ created by the Grid Layout Module’s auto-placement algorithm.

To be clear, Grid allows and encourages authors to define the size of any additional rows or columns the auto-placement algorithm may ultimately create to account for any grid items not ‘explicitly’ assigned to an area. I will introduce the properties that enable this functionality shortly.

An abstract model for the grid we just made

Rough picture of grid created from six-line value for ‘grid-template-areas’

I hope you are starting to understand one of the many powers Grid gives you, the CSS author:

  • Model even the most robust grid structures using highly visual ASCII-art syntax

“Why did you use the same word a bunch of times?”, you may be asking. I’m glad you asked. We will come to that shortly.

No screenshot for this step

That’s because we merely defined our grid’s model. We won’t see progress until we place grid items into the areas that we just assigned names to.

Place grid items into named grid areas with ‘grid-area’

In the last step, through our use of ASCII-art, we defined a 6-row, 4-column grid. Thus, we have a 24-cell table-like structure to place grid items into. To make things easier for us, we named each of those 24 cells. To make things even easier, we used only four keywords:

  • total
  • digit
  • modif
  • oper

To be clear, we could have used almost* any other combination of letters.

*One major restriction is use of the word ‘span’ as it is a reserved keyword specifically for Grid.

Using the same word in several places creates a larger grid area in which content can be placed. In other words, because we used ‘digit’ in 12 places, we effectively created a 3x4 grid area that can be referenced using the keyword, ‘digit’.

Let’s place each of our nested parent divs in each of the four named areas, like this:

/* CSS for Grid thus far */
.wrapper, .subgrid {
display: grid;
}
.wrapper {
grid-template-areas:
"total total total total"
"modif modif modif oper"
"digit digit digit oper"
"digit digit digit oper"
"digit digit digit oper"
"digit digit digit oper";
}
/* What we're adding */
h1 {
grid-area: total;
}
.modifiers {
grid-area: modif;
}
.operations {
grid-area: oper;
}
.digits {
grid-area: digit;
}
View of layout after placing grid items in named grid areas using ‘grid-area’

Isn’t it remarkable when such a small amount of code seems to have a big, positive impact?

Quick review of new Grid properties used thus far

display: grid

Turn an element in a Grid container and it’s direct children into Grid items

grid-template-areas

Explicitly define the model for our grid using ASCII-art-style syntax, thereby assigning names to the table-like cells Grid creates

grid-area

Explicitly place grid items in a named grid area

Order of operations for the remainder of this lesson

We need to address each of the four sections in order to achieve the layout demonstrated at the beginning of this lesson. I chose to go in a specific order, one you might call ‘counter-clockwise’:

The manner in which we will complete this lesson: ‘counter-clockwise’ by grid-area

The reason for this is simple: it’s similar to how Grid’s algorithm works. Unlike properties such as ‘margin’ or ‘padding’, whose list of values represent a box’s top, right, bottom and left sides, Grid works in the following order:

  1. row-start (top, if you will)
  2. column-start (left, if you will)
  3. row-end (bottom, if you will)
  4. column-end (right, if you will)

Align the calculator’s total to the bottom-right of its area

The level-1 heading that is our calculator’s totals is currently a block-level element by default. The desired effect is to align it to the bottom-right edge of the ‘total’ grid area.

To do this, we’ll leverage properties and values from the other two new layout modules, Flexbox and Box Alignment.

Code snippet

h1 {
display: flex;
justify-content: flex-end;
align-items: flex-end;
}

Technically speaking

These three statements effectively turn the h1 into a Flex container, position its contents along the end of the Flex container’s main axis, and along the end of the Flex container’s cross axis.

Calculator total indicator properly aligned using Flexbox and Box Alignment

Re-flow Modifier grid items into single evenly-distributed row

As the heading implies, we have two jobs to do:

  1. Change the Modifier items from stacking vertically (in one column) to horizontally (in one row)
  2. Be explicit that each item should be identical in size when stretched or shrunk

First, let’s use ‘grid-auto-flow’

By default, ‘grid-auto-flow’ is set to ‘row’. This means it will add a new row whenever it needs more space to place remaining grid items.

We instead want it to add a new column in such situations. Accomplishing this is straightforward.

Code snippet

.modifiers {
grid-auto-flow: column;
}

With that, we are 99% done with the Modifiers area, as per this screenshot:

Layout with ‘grid-auto-flow’ changed from default ‘row’ to ‘column’

Unfortunately, the columns are not all the same width. For this app, that is a requirement.

Get flexible grid tracks using the new ‘fr’ unit

That heading had two new terms in it. Let me elaborate.

  • A ‘grid track’ is essentially another word for a single column or row.
  • The ‘fr’ unit is essentially a fractional unit. It tends to be compared to ‘flex-basis’ in the Flexbox Layout Module. The important thing to remember is it uses ratios to calculate an element’s size. Oh, and that the ‘fr’ unit is another one of Grid’s most powerful features.

Technically speaking

What we want to do is tell Grid how to calculate the size of any implicitly-generated columns.

Code snippet

.modifiers {
grid-auto-flow: column;
grid-auto-columns: 1fr;
}

Before-and-after GIF

Before: column size not defined. After: columns told to be ‘1fr’, or 1 fractional unit

Our layout thus far

The Modifier area now contains all three items in a single evenly-spaced row

Layout digits with the power of Flexbox

Quick note: in order to make the following CSS have the desired effect, I had to order digits in reverse in the HTML (i.e. starting with 9 instead of 0).

First, Turn a Grid item into a Flex container

Code Snippet

.digits {
display: flex;
}
Setting ‘display: flex’ on ‘digits’ div appears to just stretch each button’s height

Next, specify sizing parameters for each Flex item

Code snippet

.digits {
display: flex;
}
.digits button {
flex: 1 0 30%;
/* flex-grow: 1; flex-shrink: 0; flex-basis: 30%; */
}
Effect of setting ‘flex’ on each child button of ‘digits’ Flex container. Digits 6 and lower overflow to the right.

Lastly, remove overflow of items by telling Flex container to ‘wrap’ items

Code snippet

.digits {
display: flex;
flex-wrap: wrap;
}
.digits button {
flex: 1 0 30%;
}
Effect of setting ‘flex-wrap: wrap;’ on Flex container. It’s finally starting to come together!

Putting digits in the correct order and stretching the zero

Right now the order of the digits seems mirrored horizontally: the numbers in the right-hand column should be swapped with the numbers in the left-hand column. Luckily, this can be remediated with one CSS statement thanks to how I ordered these digits in the HTML.

First, reverse the order of digits

Code snippet

.digits {
display: flex;
flex-wrap: wrap;
flex-direction: row-reverse;
}
.digits button {
flex: 1 0 30%;
}
Effect of ‘flex-direction: row-reverse;’ is digits are laid out in opposite direction along main axis of Flex container

Next, stretch the zero to take up the space of two digits

Code snippet

.digits {
display: flex;
flex-wrap: wrap;
flex-direction: row-reverse;
}
.digits button {
flex: 1 0 30%;
}
.digits .wide {
flex: 2 0 60%;
/* flex-grow: 2; flex-shrink: 0; flex-basis: 60%; */
}

The zero button will now start at double the width of a regular button, and grow at a ratio of 2:1 instead of the normal 1:1.

Effect of ‘flex’ on zero button: it now rightly is as wide as two buttons

Lastly, position the zero in the bottom-left corner

Technically speaking

Grid and Flexbox support a property called ‘order’. All elements begin with an ‘order’ of ‘0’ and will be placed according to their source order as expected. Specifying an ‘order’ other than ‘0’ will create a new group, and the browser will place any items that share the same ‘order’ value according to their source; but will place the entire group before or after the ‘0’ group, depending on whether the value is less than (before) or greater than (after) 0.

In layman’s terms

We have not yet specified any ‘order’ on any digit item, so they are all ‘order: 0’. To place the zero button in the bottom-left corner — effectively after the decimal point button — we need to give it an ‘order’ of greater than ‘0’.

Code snippet

.digits {
display: flex;
flex-wrap: wrap;
flex-direction: row-reverse;
}
.digits button {
flex: 1 0 30%;
}
.digits .wide {
flex: 2 0 60%;
order: 1;
}
Viola! Adding ‘order: 1’ moves the zero button to the right location.

Final order of business: make all buttons the same width

Right now, the digits and the row of modifiers (as I’m calling them) are the same width. However, they are wider than the column of orange operator buttons on the right. We want each button to be the same width (and height, actually).

The magical ‘fr’ fractional unit to the rescue, again

We must return to the outer-most Grid container: our ‘wrapper’ — the div with a class of ‘wrapper’.

The goal is to tell Grid how to size each column that it implicitly creates based on the template areas it creates.

Code snippet

.wrapper {
grid-template-areas:
"total total total total"
"modif modif modif oper"
"digit digit digit oper"
"digit digit digit oper"
"digit digit digit oper"
"digit digit digit oper";
grid-auto-columns: 1fr;
}

And with that, our calculator appears complete!

Calculator with all buttons identical width and height

Final review of the Grid, Flexbox and Box Alignment properties you learned

display: grid

Turn an element in a Grid container and it’s direct children into Grid items

grid-template-areas

Explicitly define the model for our grid using ASCII-art-style syntax, thereby assigning names to the table-like cells Grid creates

grid-area

Explicitly place grid items in a named grid area

grid-auto-flow

Tell Grid what direction to place items in. Default is ‘row’. Can also be ‘column’. Optional second value, ‘dense’, is another super powerful feature of Grid not covered in this lesson.

grid-auto-columns

Tell Grid how to size each implicitly created column grid track

display: flex

Turn an element into a Flex Container and its direct children into Flex Items

flex

Shorthand for setting three properties at once: flex-grow, flex-shrink and flex-basis.

flex-wrap

Specify whether the contents of a Flex Container should overflow in the direction of the main axis, or should wrap onto a new line in the direction of the cross axis.

flex-direction

Set the direction that Flex items should be placed. Default is ‘row’. Can also be ‘column’ or the reverse of both via ‘row-reverse’ and ‘column-reverse’.

order

Enables authors to arbitrarily place elements visually in a way that is completely unrelated to their source order.

justify-content

The visual effect of this property may differ based on the Layout method used and how each axis is set. For left-to-right writing mode, the default effect is justifying items horizontally.

align-items

The visual effect of this property may differ based on the Layout method used and how each axis is set. For left-to-right writing mode, the default effect is aligning items vertically.

Make it this far? You’re a rockstar.

Go forth and do great things with Grid, Flexbox and Box Alignment. Share what you make, too, so we can all learn.

Here are the same important links from the beginning of this lesson

--

--