Advertisement
  1. Web Design
  2. HTML/CSS
  3. HTML

Merry Gridmas! Building a Festive Advent Calendar With CSS Grid

Scroll to top
Read Time: 11 min

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):

Happy children celebrating ChristmasHappy children celebrating ChristmasHappy children celebrating Christmas
Happy children celebrating Christmas
Group of children and christmas treeGroup of children and christmas treeGroup of children and christmas tree
Group of children and christmas tree

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

CSS Grid Courses on Tuts+

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Web Design tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.