Creating the CSS Christmas Calendar
Tags: dohackathon, css, design, tutorial
Published: 2021-01-06 19:50:02

Creating the [CSS Christmas Calendar](https://csschristmascalendar.com) items was a lot of fun, but before I could've even started on them, I actually had to build the calendar itself - only using HTML and CSS.

It was a lot easier than I expected, with CSS techniques such as CSS grid, flexbox or 3D transforms.

## The Layout

The layout can be achieved with CSS grid, which allows us to position the calendar doors to their desired position.

![Explanation of the CSS grid structure](https://dev-to-uploads.s3.amazonaws.com/i/gbz6qarzbax10xuzhlfh.png)

```html
<div class="calendar-grid">
    <div class="title">...</div>
    <div class="day day-1">...</div>
    <div class="day day-2">...</div>
    ...
    <div class="day day-24">...</div>
</div>
```

This simple HTML can be turned into the given layout by just these few CSS rules:

```css
.calendar-grid {
  display: grid;
  
  grid-template-columns: repeat(6, 1fr);
  grid-template-rows: auto;
  grid-gap: 1rem;

  grid-template-areas:
      "title  title  title  day5   day17  day15"
      "title  title  title  day11  day20  day16"
      "title  title  title  day1   day18  day12"
      "day6   day22  day14  day24  day24  day4"
      "day10  day21  day2   day24  day24  day8"
      "day3   day9   day7   day13  day23  day19";
}
```

and then assigning each calendar door `DIV` to the corresponding template area:

```css
.title {
  grid-area: title;
}

.day-1 {
  grid-area: day1;
}

...

.day-24 {
  grid-area: day24;
}
```

Once the *calendar-grid*'s `display` property is set to `grid`, I could define the grid layout: 
- the `grid-template-columns` create 6 columns with equal width *(1 fraction)* and the number of rows is `auto`., so there can be as many as necessary.
- the `grid-gap` defines the space between the calendar doors (and title).
- the `grid-template-areas` shows, where each item should fit inside the grid layout. (these names must match the ones assigned to the individual items, as you could see).

Notice, that the `.title` element takes up a 3x3 square. That is not a problem with CSS grid, it allows grid areas that take up more than one template areas.

### What About Mobile?

There's no way 6 columns could comfortably fit on mobile devices, but they don't need to.

![The calendar on an iPhone](https://dev-to-uploads.s3.amazonaws.com/i/eyfmpusfnvn4mf7r04rz.jpg)

```css
.calendar-grid {
  grid-template-columns: repeat(3, 1fr);

  grid-template-areas:
    "title  title  title"
    "day22  day3   day8"
    "day9   day18  day11"
    ...
}
```

With a `media-query` I targeted smaller resolution devices and changed the grid template column count, and the area allocation.

This time the title takes up the top 3 areas.

## The Title

I wanted to create the title in the Christmassy spirit, so I used a Google font [Mountains of Christmas](https://fonts.google.com/specimen/Mountains+of+Christmas):

![The title](https://dev-to-uploads.s3.amazonaws.com/i/2giqnay2c2givk3a4mxb.png)

```css
@import url("https://fonts.googleapis.com/css2?family=Mountains+of+Christmas:wght@700&display=swap");
```

I broke up the whole title into 3 `span`s, applied different colors on them and rotated them individually:

```html
<div class="title">
  <h1>
    <span class="title-1">CSS</span>
    <span class="title-2">Christmas</span>
    <span class="title-3">Calendar</span>
  </h1>
</div>
```

```css
.title {
  grid-area: title;
  display: flex;
  align-items: center;
  justify-content: center;
}

.title-1 {
  color: #9c163f;
  display: block;
  transform: rotate(-10deg);
}
```

As with the layout, I also used a slightly different style for the title for mobile devices: smaller fonts, and the 3 `span`s with `inline-block` display, so they fit next to each other.

## Creating the Doors

The doors can open, close, and show the day's surprise CSS art and its title.

![Opening/closing the calendar doors](https://dev-to-uploads.s3.amazonaws.com/i/0r8zqd7mhtg59kdnfs7d.gif)

To detect the opened/closed state without using JavaScript, I used a well-known trick: added an invisible checkbox, and a related label (that contains the CSS art). Clicking on the label will trigger the checkbox, and in CSS I can target it with the `input:checked` selector.

```html
<div class="day day-1">
  <label>
    <input type="checkbox"/>
    <div class="door">
      <div class="front">1</div>
      <div class="back"></div>
    </div>
    <div class="inside">
      ... CSS art of the day ...
    </div>
    <div class="title-container">
      ... title of the CSS art ...
    </div>
  </label>
</div>
```

To hide the checkbox, simply add:
```css
.calendar-grid input {
  display: none;
}
```

The `label` has multiple functions:
- gives a height to the door/item
- changes the mouse cursor to pointer
- creates a perspective to the door (so there's a 3D effect when it's opening or closing)

```css
.calendar-grid label {
  perspective: 1000px;
  transform-style: preserve-3d;
  cursor: pointer;
  display: flex;
  min-height: 100%;
  width: 100%;
  height: 136px;
  position: relative;
}
```

(there are some small changes in the height for mobile layout, where the height is calculated from the viewport's width: `height: calc(85vw / 3);`)

The `.door` div has the animation configured:

```css
.calendar-grid .door {
  transform-style: preserve-3d;
  transition: all 300ms;
  transform-origin: 0% 50%;
  ... further styles
}

.calendar-grid input:checked + .door {
  transform: rotateY(-180deg);
}
```

First I set the transform-style, so the door's child elements will also preserve the 3D effect.  
I set that any change should be animated in 0.3 seconds.  
Finally, defined that transform animation should start from the middle of the left side. This is needed for the flip effect, so the door opens in the 3D space:

![Transform origin with opening door](https://dev-to-uploads.s3.amazonaws.com/i/4tzhfm4nuqmteofft4gi.jpg)

When the input is checked, I transform the rotation of the door, and the combination of this and the previously discussed styles end up with a nice opening/closing animation.

However, the door has two sides: the outside with the number, and a gray back.
To achieve this, I used the `backface-visibility: hidden` style. This means that if an element is rotated in the 3D space, its back is not showing up:

```html
<div class="door">
  <div class="front">1</div>
  <div class="back"></div>
</div>
```

```css
.calendar-grid .door div {
  position: absolute;
  height: 100%;
  width: 100%;
  -webkit-backface-visibility: hidden;
  backface-visibility: hidden;
  
  ... further styles ...
}

.calendar-grid .door .back {
  background: linear-gradient(to right, #384044, #2e454f);
  transform: rotateY(-180deg);
}
```

By applying `transform: rotateY(-180deg)` on the `.back` div, I could flip it (the same way how the opening animation flips, but this time staying in the same place, to show up as the back of the door).

### Ordering the Doors

I realized that if I don't set the order of the doors, they can cover each other in a weird way, or the title of an open door can get covered by another door that is in the next row.

To avoid these glitches, I manually set the `z-index` on every door, starting from the bottom left corner:

![The z-index ordering](https://dev-to-uploads.s3.amazonaws.com/i/ogpim36zwcmlodnsklw5.jpg)

```css
.calendar-grid .day-1 {
  z-index: 16;
}
.calendar-grid .day-2 {
  z-index: 9;
}
...
.calendar-grid .day-24 {
  z-index: 14;
}
```

It's important to mention, that the `z-index` ordering differs in the mobile layout, as the doors are in different locations. Therefore they need to be actualized by targeting the days with media queries.

## The Titles

Each CSS art has a title, that's a link for the individual piece's CodePen.

![Title of a CSS polar bear](https://dev-to-uploads.s3.amazonaws.com/i/mn51u8i8ja7f0ob4xwd5.png)

To make it easily noticeable that it's a link, it plays an animation to the striped background, once the mouse is over it.

The titles are inside the label, next to the door:
```html
<div class="day day-6">
  <label>
    <input type="checkbox"/>
    <div class="door"> ... </div>
    <div class="inside"> ... </div>

    <div class="title-container">
      <a href="https://codepen.io/johnnyfekete/pen/qBaRZXV" target="_blank" title="Link to source code">
        Polar bear
      </a>
    </div>
  </label>
</div>
```

To trigger the entering/leaving animations, I added these styles:
```css
.calendar-grid .title-container {
  opacity: 0;
  transform: translateY(-1rem);
  pointer-events: none;
  transition: all 400ms ease-in-out;

  ... other styles for positioning and layout ...
}

.calendar-grid input:checked ~ .title-container {
  opacity: 1;
  transform: translateY(0);
  pointer-events: all;
}
```

With these styles, the title is initially hidden (`opacity: 0`) and moved up (`transform: translateY(-1rem)`). It doesn't detect any mouse events.

Once the checkbox is checked, it animates the opacity and the position in 0.4 seconds.

### Striped Background of the Titles

The red-white striped border is not really a border, but the background of the title, positioned behind the white background.

```css
.calendar-grid .title-container a {
  /* this is the actual title with white background */
  position: relative;
  border-radius: 0.25rem;
  background-color: #1d3557;

  ... additional styles for the text ...
}

.calendar-grid .title-container a::before {
  /* pseudo class behind the title,
     with a negative offset in each direction */
  content: "";
  display: block;
  position: absolute;
  top: -0.5rem;
  right: -0.5rem;
  bottom: -0.5rem;
  left: -0.5rem;
  z-index: -1;

  border-radius: 0.75rem;

  /* the striped background uses repeating linear gradient */
  background: repeating-linear-gradient(
    -45deg,
    #f1faee 0,
    #f1faee 0.5rem,
    #e63946 0.5rem,
    #e63946 1rem
  );
  background-size: 1.44rem 1.44rem;

  ...
}
```

Finally, for the animation on `hover` I defined a `keyframe-animation` to move the background's position:

```css
@keyframes calendar-item-link {
  0% {
    background-position: 0 0;
  }
  100% {
    background-position: 1.44rem 0;
  }
}
```

This moves the background to the left with 1.44rem (exactly the same width as the width was for the stripe pattern *(fun fact, I used the Pythagorean theorem to calculate this number, as the stripes are each 0.5rem wide (1rem the red + white in total).*

I applied this `keyframe-animation` on the striped background pseudo-element:

```css
.calendar-grid .title-container a::before {
  ... rest of the styles ...

  animation: calendar-item-link 0.6s infinite linear;
  animation-play-state: paused;
}
```

so it's repeating forever, without any easing (and each cycle takes 0.6 seconds).
I also paused the animation by default, and added a hover state that plays it once the mouse is over:

```css
.calendar-grid .title-container a:hover::before {
  animation-play-state: running;
}
```

## And there you have it ✨

Creating the calendar was quite challenging, but I also had a lot of fun with it!

I learned the lesson that of course JavaScript is useful in many situations, but it is always easy for a developer to overuse it.
This project demonstrated how much can be achieved by using CSS and HTML only.

The full project is available on Github at https://github.com/johnnyfekete/CSSChristmasCalendar/.

The code in this article focused on the main features, and in the real calendar, I used more verbose CSS with vendor prefixes where necessary.

Go ahead and give it a try!