Building a Damn Simple Calendar Layout with CSS Grid, Moment, and Vue

Codepen Included

A couple of days ago, a client texted me a photo of a very simple calendar layout as inspiration for a component we were trying to add to a website we are developing. I understood his logic. We were working on a very stark typography heavy website and this calendar design perfectly fit that aesthetic. Laying something like this out statically in CSS using the grid is pretty straight forward but I wanted to make sure we could produce it dynamically as this will turn into a simple date picking component. You can check out the final calendar on Codepen and continue reading for a breakdown.

First things first, make sure you have an element we can initialize our Vue app on. For me, that’s a <main> with the id “calendar.” Add the Vue.js script to your project and initialize a simple app:

const app = new Vue({
el: '#calendar',
data() {
return {
days: []
}
},
mounted() {
// load days
}
})

Very nice. Now lets include moment and write the mounted() code necessary to fill our days array with the days of the month. In order to this, we’ll use a couple of moment functions alongside some ES6 magic. Again, in your mounted() function.

let monthDate = moment().startOf('month')
this.days = [...Array(monthDate.daysInMonth())].map((_, i) => {
return monthDate.clone().add(i, 'day')
})

First, we’ll get the beginning of the month using the startOf function. We’ll then use daysInMonth as part of a new temporary Array which will be spread into an array that equals the length of days in the month. We can then use this array to map our days by using the iterator index with moment’s clone and add to increment through each day of the month.

Now that you have an array of days, you can expand the HTML template to include some Vue list rendering. Simply add this to your app container.

<div v-for="(day, index) in days">
{{ day.format('D') }}
</div>

You should now see a list of days in your view that equal the amount of days in your current month. Now lets add some simple CSS to lay this thing out. I want the calendar centered nicely in the middle of our page, rendered in a sans-serif font, so let’s do that first.

html, body{
height: 100%;
width: 100%;
}
body{
align-items: center;
box-sizing: border-box;
display: flex;
font: calc(2vh) "Helvetica", sans-serif;
justify-content: "center";
padding: 2em;
}

Next up, we’ll setup our grid to repeat 7 columns over the available space and keep our calendar width reasonable on larger screens. Let’s also set each of our grid items to flex their numbers to the center. Finally, we’ll add a ::before style to to keep our grid items square.

#calendar{
display: grid;
grid-template-columns: repeat(7, 1fr);
max-width: 1024px;
width: 100%;
}
#calendar > *{
align-items: center;
display: flex;
justify-content: center;
}
#calendar > *::before{
content: "";
display: inline-block;
height: 0;
padding-bottom: 100%;
width: 1px;
}

You should now see a nice layout of days but they might be starting on the wrong column of the week. This is because we’ll need to tell the first day of the month to begin on a specific column and the remaining days will flow accordingly. You can do this simply by adding the grid-column property with the required index to the first div. Let’s use the power of Vue style bindings to handle this. First, add a style binding to your list.

<div v-for="(day, index) in days" :style="{ gridColumn: column(index) }">
{{ day.format('D') }}
</div>

This will call a method named column, passing the rendered day’s index. We can then check to see if this is the first day, and if so, use moment to return the first day’s day of the week (0–6) plus one so it relates to our grid system. Add this method within a methods object on your Vue app.

column(index) {
if (index == 0) {
return this.days[0].day() + 1
}
}

It might also be helpful to indicate the current day of the month. Let’s expand our day list render to include a Vue class binding.

<div v-for="(day, index) in days" :style="{ gridColumn: column(index) }" :class="{ today: today(day) }">
{{ day.format('D') }}
</div>

This class binding will attach the CSS class of “today” if the today method returns true. We’ll check to see if the rendered day is the same as today using moment’s isSame method. Again, in your methods object.

today(day) {
return moment().isSame(day, 'day')
}

You can then expand your CSS to include a different style for today’s date. I decided to circle it by adding a border and radius.

#calendar > *.today{
border: 0.1em solid black;
border-radius: 100%;
}

For a final touch, manually add seven more grid items to calendar for each day of the week and watch your CSS grid lay them out automatically.