Merry Gridmas! Building a Festive Advent Calendar With CSS Grid
In this tutorial we’re going to build a seasonal advent calendar using CSS Grid, SVG, and a handful of festive cheer! Let’s begin by taking a look at what we’ll be working towards–click the days to see what’s underneath:
What You’ll Need
You don’t actually need much for this tutorial, just a code editor (lean on CodePen to make things easier) and some basic HTML and CSS knowledge. You will need some festive visuals though–I’ve taken illustrations by the very talented masastarus from Envato Elements (they’re vector and absolutely perfect for this):






1. A Grid is Born
For our Advent calendar we’re going to use 25 grid items; one for each of the days leading up to Christmas and another to hold a title graphic. It will look something like this on small screens:



And this on larger screens:



The turquoise grid item will hold our title graphic.
Markup
To kick things off we’ll need 25 items in a container, so let’s put some HTML together:
1 |
<section class='grid-1'> |
2 |
<div class='title'></div> |
3 |
<div class='day-1'></div> |
4 |
<div class='day-2'></div> |
5 |
<div class='day-3'></div> |
6 |
<div class='day-4'></div> |
7 |
<div class='day-5'></div> |
8 |
<div class='day-6'></div> |
9 |
<div class='day-7'></div> |
10 |
<div class='day-8'></div> |
11 |
<div class='day-9'></div> |
12 |
<div class='day-10'></div> |
13 |
<div class='day-11'></div> |
14 |
<div class='day-12'></div> |
15 |
<div class='day-13'></div> |
16 |
<div class='day-14'></div> |
17 |
<div class='day-15'></div> |
18 |
<div class='day-16'></div> |
19 |
<div class='day-17'></div> |
20 |
<div class='day-18'></div> |
21 |
<div class='day-19'></div> |
22 |
<div class='day-20'></div> |
23 |
<div class='day-21'></div> |
24 |
<div class='day-22'></div> |
25 |
<div class='day-23'></div> |
26 |
<div class='day-24'></div> |
27 |
</section>
|
Tip: Given the repetitive nature of this markup you might prefer to use a templating language like HAML. HAML will let you loop over 24 items to save you writing out each one. The following little snippet compiles into exactly what you see above:
1 |
%section.grid-1 |
2 |
%div.title |
3 |
- (1..24).each do |i| |
4 |
%div{:class => "day-#{i}"} |
When we nest even more elements within these divs you’ll appreciate the time HAML saves you!
Basic Grid Styles
Let’s now add some basic styles to establish our grid. We begin by assigning display: grid;
to our container element. Then, after some dimensions, we define the grid items.
1 |
/* mobile first grid layout */
|
2 |
.grid-1 { |
3 |
display: grid; |
4 |
width: 96%; |
5 |
max-width: 900px; |
6 |
margin: 2% auto; |
7 |
|
8 |
grid-template-columns: repeat(3, 1fr); |
9 |
grid-template-rows: auto; |
10 |
grid-gap: 25px; |
11 |
|
12 |
}
|
We’ve gone “mobile first”, so you’ll see we’re defining just three columns to start with; we’ll use a media query in a moment to allow for larger screens.
-
grid-template-columns: repeat(3, 1fr);
gives us three columns, each of equal width (1fr unit) -
grid-template-rows: auto;
is actually the default value, but it states that the rows will have no specific height attributed to them–we can add as many as we like and they’ll grow with the content. -
grid-gap: 25px;
defines our gutters.
That’s really all we need, Grid can handle things from here if we leave it, but we want to be more specific about where we place each grid item. For that reason we’ll use Grid Areas.
Grid Areas
We can assign a name to each area of the grid we defined, and we can write it in a really visual way too:
1 |
grid-template-areas: "t t t" |
2 |
"d23 d20 d12"
|
3 |
"d2 d14 d4"
|
4 |
"d5 d22 d16"
|
5 |
"d1 d7 d9"
|
6 |
"d10 d11 d18"
|
7 |
"d13 d3 d15"
|
8 |
"d6 d17 d8"
|
9 |
"d19 d24 d21"; |
Here we’re naming the first area, which spans three columns on the first row t
(where we’re going to place the title). This might not be the most helpful name, but it will do the job for now. Each of the other areas is given a name in accordance with the day numbers, and as you can see, we can put them wherever we want. The ability to lay things out “randomly” like this is perfect for an advent calendar.
Going Responsive
With the addition of a media query we can (really easily) change the layout for larger screens.
1 |
/* media query */
|
2 |
@media only screen and (min-width: 500px) { |
3 |
|
4 |
.grid-1 { |
5 |
grid-template-columns: repeat(6, 1fr); |
6 |
grid-template-areas: "t t t d2 d7 d8" |
7 |
"t t t d4 d11 d12"
|
8 |
"t t t d19 d9 d13"
|
9 |
"d6 d1 d24 d24 d21 d20"
|
10 |
"d17 d18 d24 d24 d5 d22"
|
11 |
"d3 d23 d16 d14 d10 d15"; |
12 |
}
|
13 |
|
14 |
}
|
With this media query we’re stating that for viewports wider than 500px (arbitrary figure) we’ll change the grid so it has six columns: grid-template-columns: repeat(6, 1fr);
.
And we can completely redefine the arrangement of the items, placing the days wherever we feel like it. You’ll notice that the title block now takes up three columns and three rows to the top left, and d24 takes up an area of 2×2, to give it more significance. This will become clear when we assign our grid items to the grid areas in the next step.
For now, we can’t actually see any of what we’ve done, so let’s add some provisional styles to the grid items to make things visible.
1 |
section div { |
2 |
background: #2e313d; |
3 |
padding: 40px; |
4 |
}
|
Take a look:
Need a primer on grid areas? Learn more about the whole thing in this beginner’s tutorial:
Assigning Grid Areas
We now need to assign our grid items to the areas we’ve set out. We do this as follows:
1 |
/* individual items */
|
2 |
.title { |
3 |
grid-area: t; |
4 |
}
|
5 |
.day-1 { |
6 |
grid-area: d1; |
7 |
}
|
8 |
.day-2 { |
9 |
grid-area: d2; |
10 |
}
|
11 |
.day-3 { |
12 |
grid-area: d3; |
13 |
}
|
14 |
...
|
You can see our div.title
has been assigned to grid area t
, and each day has been assigned to a corresponding area. There’s nothing to say you can’t assign .day-3
to grid-area: d16;
of course, we’re just choosing to organise things this way. How’s it looking?
2. Opening Doors
That’s the basic structure all sorted, now we need to make our CSS doors. We’re going to put two divs within each grid item–one for the front of the door, another for the back–both wrapped in another div, and we’re going to use a checkbox hack to flip the whole thing round when it’s clicked.
We begin by adding a label, checkbox, and the divs to each grid item:
1 |
<div class='day-1'> |
2 |
<label>
|
3 |
<input type='checkbox'> |
4 |
<div class='door'> |
5 |
<div class='front'>1</div> |
6 |
<div class='back'></div> |
7 |
</div>
|
8 |
</input>
|
9 |
</label>
|
10 |
</div>
|
Again, you need to do this for all 24 grid items, which is a bulky chunk of repetitive markup 245 lines long, so you might find HAML helpful. Here’s how that would look:
1 |
%div{:class => "day-#{i}"} |
2 |
%label |
3 |
%input{ :type=>"checkbox" } |
4 |
.door |
5 |
.front #{i} |
6 |
.back |
With slightly adjusted padding, this is what all those checkboxes look like now:
The Doors
Prepare yourself; what’s coming is arguably the most complex bit of this whole build. It’s a big chunk and very little works without the whole thing being present, so bear with me. We’re going to do some shifting around with 3D Transforms.
Firstly, we hide the checkboxes, then we state that we want a certain level of perspective applied to our labels (perspective: 1000px;
handles that). transform-style: preserve-3d;
states that children of the label will be displayed in a 3D space instead of along a flat plane. We then use some Flexbox styles to make sure the label fills the whole area available to it.
Some styles on the .door
element (which contains the front and back faces) then set the transition timing and tidy things up further:
1 |
/* door styles */
|
2 |
|
3 |
.grid-1 input { |
4 |
display: none; |
5 |
}
|
6 |
|
7 |
label { |
8 |
perspective: 1000px; |
9 |
transform-style: preserve-3d; |
10 |
cursor: pointer; |
11 |
|
12 |
display: flex; |
13 |
min-height: 100%; |
14 |
width: 100%; |
15 |
height: 120px; |
16 |
}
|
17 |
|
18 |
.door { |
19 |
width: 100%; |
20 |
transform-style: preserve-3d; |
21 |
transition: all 300ms; |
22 |
border: 2px dashed transparent; |
23 |
}
|
Front and Back
Okay, now to concentrate on the front and back faces of our door.
1 |
.door div { |
2 |
position: absolute; |
3 |
height: 100%; |
4 |
width: 100%; |
5 |
backface-visibility: hidden; |
6 |
|
7 |
display: flex; |
8 |
align-items: center; |
9 |
justify-content: center; |
10 |
}
|
11 |
|
12 |
.door .back { |
13 |
background-color: #2e313d; |
14 |
transform: rotateY(180deg); |
15 |
}
|
The .door div
selector points to both the .front
and the .back
divs. We use it to make each one 100% the width and height of the .door
container, and absolutely positioned top left. The Flexbox rules make sure the content within (the number) is centrally aligned and the backface-visibility: hidden;
rule ensures that neither of the faces can be seen when their reverse side is facing us. That’s important, as we then focus on the .back
and set it to flip over with transform: rotateY(180deg);
.
Now For the Fun Stuff
All this has been leading up to what’s actually a pretty cool effect. We’re going to use the checked state of the checkbox to transition the faces. When we click on the label (which fills the whole grid area) it checks and unchecks the checkbox, even though it’s not displayed. Depending on that state, we alter which way the .door
is facing.
1 |
label:hover .door { |
2 |
border-color: #385052; |
3 |
}
|
4 |
|
5 |
:checked + .door { |
6 |
transform: rotateY(180deg); |
7 |
}
|
The first rule gives us a hover state. The second rule uses an adjacent selector, so when a .door
element is immediately preceded by a :checked
element we rotate it 180 degrees on the Y axis (we flip it over). All of that gives us our fundamental door functionality!
3. Time to Decorate
Let’s make things look pretty. The first visual we’re going to slot in is our title graphic. Using the Happy children celebrating Christmas illustration I took the title, changed the details and saved an SVG file.
Tip: for detailed vectors like these, in Illustrator go to Object > Path > Simplify... and you can reduce the complexity. By trimming the number of curves and points you can significantly lower the file size without losing too much of the overall effect.



There are a few ways we could add this image to the page, but I’ve just placed an img
tag in our markup:
1 |
<div class='title'> |
2 |
<img src='merry-gridmas.svg'> |
3 |
</div>
|
Some styles give our image fluidity and align it centrally within the grid area:
1 |
.title { |
2 |
display: flex; |
3 |
align-items: center; |
4 |
justify-content: center; |
5 |
}
|
6 |
|
7 |
.title img { |
8 |
width: 90%; |
9 |
height: auto; |
10 |
}
|
To enhance the blank background we add another SVG (the snowfall and clouds) to the body via CSS:
1 |
body { |
2 |
background: url(snow-bg.svg) no-repeat top center #82d8cb; |
3 |
background-size: cover; |
4 |
}
|
Adding Images to the Doors
Each of our 24 doors also need to have an image hiding behind them. Again, we could do this in a few different ways, but in this case we’ll save a bunch of SVG files and add them as backgrounds via the CSS. For each door, after we define its grid area, we’ll have a rule for the .back
face:
1 |
.day-6 { |
2 |
grid-area: d6; |
3 |
}
|
4 |
.day-6 .back { |
5 |
background: url(snowflake.svg); |
6 |
}
|
This is what that gives us:
Pretty good! Now we just need to tidy those images up with some general rules.
Tidying Up
First up, those background images need to appear just once, so we’ll add a no-repeat
. They also need to be central and scaled properly.
1 |
.door .back { |
2 |
background-size: contain; |
3 |
background-position: center center; |
4 |
background-repeat: no-repeat; |
5 |
background-color: #2e313d; |
6 |
transform: rotateY(180deg); |
7 |
}
|
Some border-radius
added to .door
and .door div
gives us a more friendly aesthetic. And finally, we link to the font Kalam on Google fonts to polish that last typographic detail:
1 |
<link href="https://fonts.googleapis.com/css?family=Kalam" rel="stylesheet"> |
Styles:
1 |
/* added to .door div */
|
2 |
font-family: 'Kalam', cursive; |
3 |
color: #385052; |
4 |
font-size: 2em; |
5 |
font-weight: bold; |
6 |
text-shadow: 1px 1px 0 rgba(255, 255, 255, 0.2); |
Conclusion
That about wraps things up! Of course, this is really just for novelty value and practicing CSS Grid (the status of each door isn’t saved for the next browser session or anything) but we’ve come a long way with just some markup and some styles.
Support is one last point to mention; there are a few aspects of this tutorial which don’t enjoy complete browser support yet–for some tips on how to deal with this see the resources linked below.
I hope you enjoy the tutorial, and enjoy counting down the days!
Useful Resources
- Understanding the CSS Grid Layout Module (series)
-
Using CSS Grid: Supporting Browsers Without Grid by Rachel Andrew
- CSS Grid on Can I Use