Creating a React Calendar Component: Part 3

Elbert Bae
Jul 12, 2020 · 13 min read

Creating an appealing visual component can be an effective way to draw someone towards a part of your web application. In part 1 and part 2 of this series, we explored the logic behind the dates to display and the React code that used a set of dates to render a visible component in the browser. In this next part, we will explore the code that gives our component the visual appeal.

When I first began coding, I never enjoyed CSS as much as the other aspects of development. My first job had a singular CSS file controlling the entire web application’s styling. Having limited experience with CSS at the time, this was an absolute nightmare. However, that experience is what drove me to further explore the best way to structure my CSS code moving forward and in future projects. Much like other parts of development, CSS has its own design patterns and practices which were further enabled by technologies such as SASS and Styled Components.

Before we begin, the following examples will be in SASS which is a superset of CSS. SASS is compiled into CSS, but allows functionality which make organizing styling code easier. My 3 favorite aspects of SASS is that it allows nesting of CSS for specificity, creating variables for reusability, and importability of SASS files into each other. Let’s do a breakdown of what we’ll be taking a look at today:

  1. Styling the calendar header using Flexbox
  2. Styling the month indicators using Flexbox
  3. Styling the weekday indicators using Grid CSS
  4. Styling the date indicators using Grid CSS
  5. Adding in themes

Section 1: Styling the calendar header using Flexbox

Before we begin, let’s add some CSS to the main BaeCalendar component so we can see it on the browser and normalize some HTML element styles.

.bae-calendar-container {
box-sizing: border-box;
box-shadow: 0 0 3px rgba(0, 0, 0, 0.5);
height: 100%;
width: 100%;

As you can see, elements with a class called bae-calendar-container is given a 100% height and width of its parent element. This allows any users importing the component to wrap it in a different container so they can specify the height and width themselves. For now, it will take on the body element's height and width property so we can see it on the browser.

Aside from this, you’ll notice that we are taking advantage of SASS’s capability of nesting to style the h1, h2, h3... elements to normalize its default styles. Without going into great detail, nesting will translate this into a CSS code that will look like this:

.bae-calendar-container h1, .bae-calendar-container h2 {
padding: 0;
margin: 0;
line-height: 1;
font-family: 'Varela Round', sans-serif;
}
// And so on...

Above is the component with no styling that we will transform into the following.

Let’s take a moment to look at the HTML layout of the CalendarHeader component.

<div className="bae-calendar-header">
<div className="left-container">
<h1>{getReadableWeekday(selectDate)}</h1>
<h1>{getReadableMonthDate(selectDate)}</h1>
</div>
<div className="right-container">
<h3>{getYear(selectDate)}</h3>
</div>
</div>

All of the elements we see here are block elements which by definition is an element that starts a new line. So how do we end up with 2 columns? Thankfully, flexbox is a very useful CSS property we can take advantage of.

.bae-calendar-header { 
display: flex;
}

The display: flex property defines an element to act like a flex element. A flex element acts differently. Essentially, the outer element is still a block element, but any inner elements take on a more fluid form. Unless display: flex is specified, we're not able to apply any other flex based CSS properties.

Here is an example of the component with display: flex.

Notice how the inner elements are no longer acting as block elements? The container itself wrapping the contents will maintain its block behavior, but this gives us freedom to control the layout of the inner elements. Let's make it so that the readable dates and the years end up on opposite sides by adding justify-content: space-between.

.bae-calendar-header {
display: flex;
justify-content: space-between;
}

Self-explanatory right? When flex is in a row format (e.g. left-container and right-container), justify-content modifies the horizontal layout. Keep in mind, that if you are working with a flex element in a column format, justify-content will follow the new change and affect the vertical layout. The option we provide space-between does exactly what the name states. It provides an even spacing between all elements, but no spacing on the edges.

We’re making progress, but let’s see if we can provide some space to the edges and a border to show where the CalendarHeader component ends.

.bae-calendar-header {
padding: 15px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
display: flex;
justify-content: space-between;
}

Great! Let’s move on using the same flex tricks to our month indicators.

Section 2: Styling the month indicators using Flexbox

Similar to the CalendarHeader component, the MonthIndicator is a div element with 3 block elements inside.

<div className="bae-month-indicator">
<h4 data-date={monthSet.prev} onClick={changeDate}>
{monthsFull[getMonth(monthSet.prev)]}
</h4>
<h3>{monthsFull[getMonth(monthSet.current)]}</h3>
<h4 data-date={monthSet.next} onClick={changeDate}>
{monthsFull[getMonth(monthSet.next)]}
</h4>
</div>

Here’s how it looks with no styling…

And what we want to achieve…

It looks like we can apply similar flex properties so let's add the same things we did to the header.

.bae-month-indicator {
display: flex;
justify-content: space-between;
}

It looks pretty good so far. However, let me show you one thing. Although you can’t tell, these 3 elements are not perfectly centered. This is a case where it probably doesn’t matter too much, but it’s fairly simple to ensure that all elements are centered vertically with one simple rule.

.bae-month-indicator { display: flex; justify-content: space-between; align-items: center; }

The property align-items is similar to justify-content. While the flex is in a row format, while justify-content works horizontally, align-items work vertically. A quick trick to center any item is to provide the parent a display: flex and justify-content: center with align-items: center and you ensure the item is centered inside of the parent element.

Knowledge of Flexbox can be incredibly useful. One of the best ways to learn Flexbox is through an interactive game which you can checkout here!

Section 3: Styling the weekday indicators using Grid CSS

Now that we’ve taken the time to see how Flexbox is applied to the calendar component, let’s spend some time and explore the basics of Grid CSS. Similar to Flexbox, Grid is used to create the layout of the visual components in a web application. There are some differences between Flexbox and Grid, but for the most part, it is possible to achieve a similar layout result with both.

In my experience, I have some to utilise Flexbox and Grid for different scenarios. I use Flexbox primarily for positioning individual elements, but when it comes to repeated and predictable layouts, I prefer to use Grid. Let’s explore this as we style the WeekdayIndicator.

Referring back to part 2 of the series, here is how the WeekdayIndicator looks as React code:

const weekdays ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']; const WeekdayIndicator = () => { const weekdayIcons = weekdays.map((day, key) => { return ( <div className="weekday-indicator-icon" key={key}> {day} </div> ); }); return <div className="bae-weekday-indicators">{weekdayIcons}</div>; };

Which translates to the following HTML:

<div className="bae-weekday-indicators"> <div className="weekday-indicator-icon"> Sun </div> <div className="weekday-indicator-icon"> Mon </div> <div className="weekday-indicator-icon"> Tue </div> <div className="weekday-indicator-icon"> Wed </div> <div className="weekday-indicator-icon"> Thu </div> <div className="weekday-indicator-icon"> Fri </div> <div className="weekday-indicator-icon"> Sat </div> </div>

Without any styling, here is what these elements look like with their natural block behaviors.

And here is what we want it to look like after styling it.

While Flexbox can certainly be used to achieve this outcome, we are going to use a combination of Grid to get the weekdays horizontal and evenly distributed while using Flexbox to center the text inside of each div.weekday-indicator-icon elements.

Let’s start with the Grid using two properties called grid-template-columns and grid-template-rows.

.bae-weekday-indicators { display: grid; grid-template-columns: repeat(7, minmax(auto, 1fr)); grid-template-rows: 1; padding: 15px // To keep padding consistent with the other components so far }

In grid-template-columns, the value repeat(7, minmax(auto, 1fr)) is saying create 7 columns with inner elements with its minimum size to auto (change to fit) and maximum size to 1fr (1/7 of the total container size). As an example, you could make it have only 3 columns whereby it will override the grid-template-rows by overflowing and creating new rows to fit the number of inner elements inside of the parent container resulting in this.

Let’s switch it back and move on. As you can see, the weekday indicator icons are not centered which we’ll work on next by using the Flexbox concepts we used above. Since the grid lays out each of the containers wrapping the weekday texts, we need to add some styling to elements with the class name weekday-indicator-icon.

.bae-weekday-indicators { display: grid; grid-template-columns: repeat(7, minmax(auto, 1fr)); grid-template-rows: 1; padding: 15px; .weekday-indicator-icon { height: 25px; width: 25px; display: flex; justify-self: center; justify-content: center; align-items: center; padding: 5px; font-weight: bold; cursor: pointer; } }

As you can see, I applied the Flexbox trick of centering using justify-content: center and align-items: center. This ensures that the elements internal to the div.weekday-indicator-icon are centered. But, what is justify-self doing? In this case, we're applying a horizontal CSS property, but to the element by referring to itself. Here is an example of how the elements look without justify-self: center and below that, with it.

justify-self not applied
justify-self: center;

Section 4: Styling the date indicators using Grid CSS

Now that we have the grid example above with the WeekdayIndicator component, let's apply those same changes to the DateIndicator to achieve this.

As a quick refresher, here’s the DateIndicator component.

const DateIndicator = ({ activeDates, selectDate, setSelectDate }) => {
const changeDate = (e) => {
setSelectDate(e.target.getAttribute('data-date'));
};

There are two things we want to do here. One is setup the grid using the same principles above using Grid CSS. The other, is visually distinguishing between the active dates in the current month, versus the overflow dates of the previous and following months. The grid part should be simple, so here’s what we’ll add.

.bae-date-indicator {
display: grid;
grid-template-columns: repeat(7, minmax(auto, 1fr));
grid-template-rows: repeat(6, minmax(35px, 1fr));
grid-gap: 5px;
padding: 0 15px;
.date-icon {
display: flex;
justify-content: center;
justify-self: center;
align-items: center;
height: 25px;
width: 25px;
padding: 5px;
cursor: pointer;
}
}

Now, we want to fade out the overflow dates. Looking at the component code, we can see that the data attribute data-active-month={i.currentMonth} is applied. This can either be data-active-month="true" or data-active-month="false", providing us with a simple way to identify what is the current month or not in our CSS with the following change to target elements with the specificity .bae-date-indicator .date-icon[data-active-month="false"].

.bae-date-indicator {
display: grid;
grid-template-columns: repeat(7, minmax(auto, 1fr));
grid-template-rows: repeat(6, minmax(35px, 1fr));
grid-gap: 5px;
padding: 0 15px;
.date-icon {
display: flex;
justify-content: center;
justify-self: center;
align-items: center;
height: 25px;
width: 25px;
padding: 5px;
cursor: pointer;
&[data-active-month='false'] {
color: rgba(0, 0, 0, 0.3);
}
}
}

That’s it! The last thing we’re going to add is the styling for elements with the selected class name which we will use to improve our UI by making it clear to our users which date is currently selected on the calendar.

.bae-date-indicator {
display: grid;
grid-template-columns: repeat(7, minmax(auto, 1fr));
grid-template-rows: repeat(6, minmax(35px, 1fr));
grid-gap: 5px;
padding: 0 15px;
.date-icon {
display: flex;
justify-content: center;
justify-self: center;
align-items: center;
height: 25px;
width: 25px;
padding: 5px;
cursor: pointer;
&[data-active-month='false'] {
color: rgba(0, 0, 0, 0.3);
}
&.selected {
border-radius: 50%;
box-shadow: 0 0 3px rgba(0, 0, 0, 0.5);
}
}
}

At this point, putting our components together with styling should look like this.

Section 5: Adding in themes

Now that we have the layout in place for our component, let’s add a little bit of life to it by adding colors. Color matching and design is definitely not my strong suite which is why I want to make this as easy to modify as possible. To apply our themes, we’re going to take advantage of CSS specificity rules by using nesting in SASS. Here is our root bae-calendar.sass file.

.bae-calendar-container {
box-sizing: border-box;
box-shadow: 0 0 3px rgba(0, 0, 0, 0.5);
height: 100%;
width: 100%;

As you can see, this root SASS file imports multiple SASS files consisting of the styling for each of our individual sub-components that we styled above. You also see that there is a new file under a class name salmon-theme imported with the & selector to apply styles on elements with .bar-calendar-container.salmon-theme. Here is where we will outline the theme styles for our calendar component.

Inside of our salmon.sass file, we have the following:

$primaryColor: #fa8072;
$secondaryColor: #ffa98f;
$highlightTextColor: #d95e39;
$activeTextColor: #f8f8ff;

Without going into much detail, can you see how we utilise SASS variable names and the organization of the theme file? Easy to understand right? All of our texts will apply the color: $activeTextColor and we see some other changes in the background-color properties as well. Here's how the component looks with this style.

Easy right? Can you see how we can take advantage of the way this has been organized? All we need to do now is select the colors we want for the following variables:

$primaryColor: #fa8072;
$secondaryColor: #ffa98f;
$highlightTextColor: #d95e39;
$activeTextColor: #f8f8ff;

For example, monochrome-theme uses the following variables.

$primaryColor: #646e78;
$secondaryColor: #8d98a7;
$highlightTextColor: #6d7c90;
$activeTextColor: #f8f8ff;

Once we change these variables, we can copy it to a new file and import it again like this.

.bae-calendar-container {
box-sizing: border-box;
box-shadow: 0 0 3px rgba(0, 0, 0, 0.5);
height: 100%;
width: 100%;

To make things easier for our users, we can create preset themes and allow them to use the components by specifying which class names to append to the main element. As an example, our calendar component can do something like this to accept a prop called theme which translates to a specific className.

import React, { useState } from 'react';
import { getToday } from './utils/moment-utils';
import './bae-calendar.scss';

This can then be imported as such…

<BaeCalendar theme="monochrome" />

And that’s it! Hope this information provide some insight in how you can organize the styling for your components. Style files require just as much organization and design as other areas of code so make sure you don’t skip out on careful planning when you are creating your next front-end projects. In the next part, I’ll show you how the component files were structured and add a few features that will allow our component users to get the date they select back to use within their own programs as well as pass in pre-selected dates.

Dev Genius

Coding, Tutorials, News, UX, UI and much more related to development

Sign up for Best Stories

By Dev Genius

The best stories sent monthly to your email. Take a look.

By signing up, you will create a Medium account if you don’t already have one. Review our Privacy Policy for more information about our privacy practices.

Check your inbox
Medium sent you an email at to complete your subscription.

Elbert Bae

Written by

Learning enthusiast having fun with web development through mini-projects. On the side, writing about personal growth, life, and relationships.

Dev Genius

Coding, Tutorials, News, UX, UI and much more related to development

Elbert Bae

Written by

Learning enthusiast having fun with web development through mini-projects. On the side, writing about personal growth, life, and relationships.

Dev Genius

Coding, Tutorials, News, UX, UI and much more related to development

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store