Making a responsive calendar for dynamic dates using Flexbox

How to take advantage of the Flexbox layout mode and Sass to create a responsive calendar

by: Ricardo Henriquez

I was recently faced with the challenge of creating a dynamic calendar UI for an "upcoming event" section of a new website I've been developing. Knowing that mobile and tablet internet usage exceeds desktop usage worldwide, it was very important to have a responsive calendar that can be viewed on all devices and to have the calendar dates match the day of the week regardless of what day was the first of the month .

The options

Creating responsive table layouts has been around for a few years now. In 2011 Chris Coyier wrote an article that for many years was one of the best solution known to tackle the issue... and maybe it's still being used today by many.

Since then, other options have surfaced in the world of front-end development. Heres's a few:

  • Plugins like FooTable and Stacktable JS
  • Dave Bushell’s that flips the table on its side.
  • Frameworks like Bootstrap and Foundation offer a simple solution to responsive tables. Options include JS and scroll content horizontally when in small width devices. 
  • Turning a bunch of divs or list items into table-role boxes to recreate a table layout and then switching the display when viewing in smaller devices. This no better than just using table markup in the first place.

But this is 2017 and today we have features at our disposal that enjoy full browser support and don't require JS or jQuery plugins to make them compatible with all major browsers. 

Enter Flexbox

Flexbox is a CSS box model optimized for user interface design. This article is not intended to teach you about Flexbox or to claim that this the best option for your project, but overall this method offers so much flexibility that you might consider replacing all your content tables with these techniques.

One big problem that Flexbox can help us fix is the fact that the first of the month might not always land on the first day of the week, and we need a way to place it on the correct day.

Creating the layout

Let's first create a div containing the days of the week (from Monday to Sunday), followed by a div that holds all the days of the month. (I will first use hard coded HTML to create the days of the month. I will change that later).

<div class="calendar">
	<div class="days-of-week">
		<span class="day-block">Monday</span>
		<span class="day-block">Tuesday</span>
		<span class="day-block">Wednesday</span>
		<span class="day-block">Thursday</span>
		<span class="day-block">Friday</span>
		<span class="day-block">Saturday</span>
		<span class="day-block">Sunday</span>
	</div>
	<div class="days-of-month">
		<span class="day-block">1</span>
		<span class="day-block">2</span>
		...
		<span class="day-block">31</span>
	</div>
</div>
//The width of the calendar
$calendar-width: 80%;

//Total width divided by days in a week
$day-block-width: 100%/7;
.calendar {
	width: $calendar-width;
	margin: 0 auto;
	.days-of-week, .days-of-month {
		//Make the container display flex
		display: flex;
		//Move the days to the next row
		flex-wrap: wrap;
		width: 100%;
		.day-block {
			flex-basis: $day-block-width;
			text-align: center;
			// Make sure the padding and/or margins don't add to total width
			box-sizing: border-box;
			border: 1px solid black
		}
	}
	.days-of-week {
		margin-bottom: 1em;
		.day-block {
			padding: 1em;
		}
	}
	.days-of-month {
		.day-block {
			padding: 1em;
		}
	}
}

So far I have only created the skeleton for our calendar. I still need to modify the position for the first day of the month based on the day of the week.

Placing the fist day of the month under the right day

First I'll include a class of .calendar-span-{$} in our .day-block that will reflect the number for the day of the week. Remember that our week starts on Monday for this example.

I will use the month of July 2017 for this example.  The 1st landed on Thursday (day 4).

<h1>July 2017</h1>
	<div class="calendar">
	...
	</div>
	<div class="days-of-month">
	<!-- Setting the calendar-span to the number representing the day of the week -->
	<span class="day-block calendar-span-4">1</span>
	<span class="day-block">2</span>
	...
	<span class="day-block">31</span>
</div>

I can then change the flex-basis property from auto to a number that will span a number of days in the week prior to the first day of the month. I can achieve that by multiplying the width of .day-block by that number.

$calendar-span-class: calendar-span !default;

//Sass for loop to make the classes for us and calculate the flex-basis
@mixin slugs() {
	@for $i from 1 through 7 {
		.#{$calendar-span-class}-#{$i} {
		//Set the flex-basis to the width of each day-block multiplied by the number of days prior to the beggining of the month
			flex-basis: $day-block-width * $i;
		}
	}
}

Now that I have a way to span the first day of the month, let's apply some basic styling to our calendar to separate our day blocks.

I am also adding a span around the number of the month in order to style it.

The results so far look something like this:

The problem I have right now is that based on the desired styling, the first day of the month takes the width of all previous days in the week combined. To solve that, I'll add another span inside the first .day-block and set its width same as all other elements with a class of .day-block.

<div class="days-of-month">
	<span class="day-block calendar-span-4">
		<span class="day-block-inner">
			<span class="number">1</span>
		</span>
	</span>
	<span class="day-block"><span class="number">2</span></span>
	...
</div>
@mixin slugs() {
	@for $i from 1 through 7 {
		.#{$calendar-span-class}-#{$i} {
			flex-basis: $day-block-width * $i;
			display: flex;
			background: transparent !important;
			.day-block-inner {
				// Getting the width of the day-block-inner
				$day-block-inner-basis: 100% / $i;
				flex-basis: $day-block-inner-basis;
			}
		}
	}
}
.days-of-month {
	// Adding the same styles to the .day-block-inner as all other day-blocks
	.day-block, .day-block-inner {
		background: $gray-light;
		display: flex;
		//Make all day-numbers align toward to end line
		justify-content: flex-end;
		min-height: $day-number-size *2;
		.number {
			color: $white;
			background: $main-color;
			margin: $day-block-gutter;
			display: block;
			width: $day-number-size;
			height: $day-number-size;
			font-size: .8em;
			line-height: $day-number-size;
		}
	}
}

But wait, it's not responsive!

Yes, so far I have only created a desktop view, and please know that I am well aware of Mobile-First Responsive Web Design and the importance of prioritizing the mobile content when creating user experiences, but my focus so far was to create a calendar UI that was able to shift the first day of the month based on the day of the week. Now that I have accomplished that task I will style the calendar for smaller screens.

I will refactor my Sass file to reflect a different layout for different browser widths. (For the purpose of this article, I will only create 1 breakpoint)

$gray:                #cccccc;
$gray-light:          #eeeeee;
$main-color:          #0D8CB2;
$white:               #ffffff;
$day-block-gutter:    4px;
$day-number-size:     25px;
$breakopoint-width:   960px;
$breakpoint:          "(min-width:#{$breakopoint-width})" !default;
$calendar-width:      80%;
$day-block-width:     100%/7; 
$calendar-span-class: calendar-span !default;

@mixin slugs() {
	@for $i from 1 through 7 {
		.#{$calendar-span-class}-#{$i} {
			flex-basis: $day-block-width * $i;
			display: flex;
			background: transparent !important;
			.day-block-inner {
				$day-block-inner-basis: 100% / $i;
				flex-basis: $day-block-inner-basis;
				outline: $day-block-gutter solid white;
				outline-offset: -$day-block-gutter;
			}
		}
	}
}

body {
	font-family: "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif; 
}

h1 {
	text-align: center;
	color: $main-color;
}
.calendar {
	width: 95%;
	margin: 0 auto;
	.days-of-week, .days-of-month {
		display: flex;
		flex-wrap: wrap;
		width: 100%;
		.day-block {
			flex-basis: 100%;
			text-align: center;
			box-sizing: border-box;
			margin-bottom: $day-block-gutter;
		}
	}
	.days-of-week {
		margin-bottom: 1em;
		display: none;
		.day-block {
			height: 30px;
			background: $main-color;
			color: $white;
			text-transform: uppercase;
			font-size: .8em;
			letter-spacing: .1em;
			line-height: 30px;
		}
	}
	.days-of-month {
		.day-block, .day-block-inner {
			background: $gray-light;
			display: flex;
			min-height: $day-number-size *2;
			flex-basis: 100%;
			&:nth-of-type(even) {
				background: darken($gray-light, 10%);
			}
			.number {
				color: $white;
				background: $main-color;
				display: block;
				height: 100%;
				width: $day-number-size * 2;
				font-size: 1em;
				line-height: $day-number-size * 2;
			}
		}
	}
	@media #{$breakpoint} {
		width: $calendar-width;
		.days-of-week, .days-of-month {
			display: flex;
				.day-block, .day-block-inner {
					justify-content: flex-end;
					flex-basis: $day-block-width;
					outline: $day-block-gutter solid white;
					outline-offset: -$day-block-gutter;
					margin-bottom: 0;
					.number {
						font-size: .8em;
						margin: $day-block-gutter;
						width: $day-number-size;
						height: $day-number-size;
						line-height: $day-number-size;
					}
				}
				@include slugs;
			}
		.days-of-month {
			.day-block, .day-block-inner {
				&:nth-of-type(even) {
					background: $gray-light;
				}
			}
		}
	}
}

Up until now, I've created the calendar using static content to populate my calendar dates. All the focus has been on styling elements and not on fetching real data. You can see the progress so far in this Codepen.

Dynamically generated dates

For the purpose of this example, I will be using Javascript to generate our calendar dates, but you can modify it to use any other programming language by borrowing the same logic.

//JS is used only to generate the the calendar dates dynamically and not to create the layout or styles.

var now              = new Date();
var currentMonth     = now.getMonth()+1;
var month            = now.getMonth();
var currentYear      = now.getFullYear();
var daysInMonthCount = new Date(currentYear, currentMonth, 0).getDate();

// Getting the Name of the month for our header
var monthNames = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"
];
var currentMonthName = (monthNames[month]);

// Getting the day of the week the month starts
var firstDayOfMonth = new Date(currentYear + "-" + currentMonth + '-01').getDay();

// Creating a variable to attach the firstDayOfMonth variable to the calendar-span class
// to take advantage of the flex-basis code
var calendarSpan = 'calendar-span-' + firstDayOfMonth;

// Adding the calendar header with the current month and year
var header = document.getElementById('calendar-header');
header.innerHTML = header.innerHTML + currentMonthName + " " + currentYear;

// Loop through all days in the month
for (i = 1; i <= daysInMonthCount; i++) {
	// Making a variable with the HTML code for all days in the month
	// I will insert the day in number to the span with class of number
	var daysCalendarBlock = 
		'<span class="day-block">'+
		'<span class="number">' + i + '</span>'+
		'</span>';
	
	// Making a variable for the fist day day-block with the HTML code. 
	// I also have to include the calendarSpan variable that will move the day-block to the corresponding day in the calendar
	var firstDayofCalendarBlock = 
		'<span class="day-block ' + calendarSpan + '">' +
			'<span class="day-block-inner">' +
				'<span class="number">' + i + '</span>'+
			'</span>' +
		'</span>';

	// Appending the firstDayOfCalendar variable to the first of the month
	if ( i === 1)  {
		var firstDayOfCalendar = document.getElementById('days-of-month');
		firstDayOfCalendar.insertAdjacentHTML('beforeend', firstDayofCalendarBlock);
	} else {
	// Appending the daysOfCalendar variable to the days of the month
		var daysOfCalendar = document.getElementById('days-of-month');
		daysOfCalendar.insertAdjacentHTML('beforeend', daysCalendarBlock);
	}
};

Conclusion

Flexbox is w3c’s answer to what web developers have been hacking together with older tools. I use it for almost all positioning related styling because it is simply so versatile, and unlike other CSS attributes is not just a single property, but more of a parent child relationship with a ton of other options to adjust things. 

It is worth mentioning that flexbox is designed for component layouts inside pages, but when dealing with full page layouts, its is better to use a CSS3 Grid Layout, which now enjoys full partial support.

Other resources: