Celebrate CSS Grid support by re-creating the iOS Calculator
This is what it should* look like when we’re done.
*In a Grid-supporting browser, that is. As of this writing, that includes Firefox 52 and Chrome 57.
TL;DR
- Completed exercise on Codepen
- Collection of all exercises on Codepen
- CSS Grid Layout Module Level 1 specification
- Follow Rachel Andrew to learn Grid the right way
- Looking for more great Grid resources? Learn CSS Grid
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>
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;
}
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>
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
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;
}
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 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:
- row-start (top, if you will)
- column-start (left, if you will)
- row-end (bottom, if you will)
- 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.
Re-flow Modifier grid items into single evenly-distributed row
As the heading implies, we have two jobs to do:
- Change the Modifier items from stacking vertically (in one column) to horizontally (in one row)
- 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:
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
Our layout thus far
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;
}
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%; */
}
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%;
}
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%;
}
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.
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;
}
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!
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
- Completed exercise on Codepen
- Collection of all exercises on Codepen
- CSS Grid Layout Module Level 1 specification
- Follow Rachel Andrew to learn Grid the right way
- Looking for more great Grid resources? Learn CSS Grid