Building a calendar component with Tailwind and date-fns
Vivek Alhat
Posted on April 1, 2023
A calendar component is one of the most common and useful component on the web. It can be used for displaying events, scheduling reminders and for many other different use cases.
Over a last few days, I have been exploring Tailwind CSS a lot and I wanted to try building something that is used more commonly across the web.
In this article, we are going to build a simple calendar component using Tailwind which is a popular utility-first CSS framework, date-fns which is a popular library for manipulating date and time using JavaScript.
The calendar component that we are going to build in this article will have following features:
- Display current month by default
- Get next month
- Get previous month
- Highlight current day
So, let's dive right in!
Installation
I assume that you have already set up a basic React project. You can install Tailwind CSS by following different guides mentioned in installation section of Tailwind website.
Now that we have set up a basic React + Tailwind
project, let's go ahead and install date-fns
library.
If you are using npm
then run npm install date-fns
command to install the package. If you are using yarn
then run yarn add date-fns
command.
Now we have everything installed that we need to build this component.
Building the component
Let's start building this component.
First we will a write code to display the current month and year. We will also include buttons to toggle between previous and next month. Here is what we are going to build in first part.
I am using heroicons (also created by makers of Tailwind CSS) to create buttons for fetching next and previous month.
Here is the JSX that will render a calendar component controls UI.
<div className="flex items-center justify-between">
<p className="font-semibold text-xl"> {format(firstDayOfMonth, "MMMM yyyy")} </p>
<div className="flex items-center justify-evenly gap-6 sm:gap-12">
<ChevronLeftIcon className="w-6 h-6 cursor-pointer" onClick={getPrevMonth} />
<ChevronRightIcon className="w-6 h-6 cursor-pointer" onClick={getNextMonth} />
</div>
</div>
Here are the functions and state that we'll need to set up for this part.
const today = startOfToday();
const [currMonth, setCurrMonth] = useState(() => format(today, "MMM-yyyy"));
let firstDayOfMonth = parse(currMonth, "MMM-yyyy", new Date());
const daysInMonth = eachDayOfInterval({
start: firstDayOfMonth,
end: endOfMonth(firstDayOfMonth),
});
const getPrevMonth = (event: React.MouseEvent<SVGSVGElement>) => {
event.preventDefault();
const firstDayOfPrevMonth = add(firstDayOfMonth, { months: -1 });
setCurrMonth(format(firstDayOfPrevMonth, "MMM-yyyy"));
};
const getNextMonth = (event: React.MouseEvent<SVGSVGElement>) => {
event.preventDefault();
const firstDayOfNextMonth = add(firstDayOfMonth, { months: 1 });
setCurrMonth(format(firstDayOfNextMonth, "MMM-yyyy"));
};
Let's understand the different functions from above code.
-
format
function is provided bydate-fns
library. It is basically used for formatting the given date. It returns formatted date string. -
startOfToday
function is also provided bydate-fns
and it returns current date. -
parse
function provided bydate-fns
is used for parsing the date from given date string. -
eachDayOfInterval
function provided bydate-fns
returns an array of dates within specified date interval. -
getPrevMonth
function is used for getting dates for a previous month. It usesadd
function fromdate-fns
and we are settingmonth
to-1
to fetch first day of previous month. Once we get first day of previous month. We can re-calculate and get dates usingeachDayOfInterval
function. -
getNextMonth
function is similar togetPrevMonth
except we are settingmonth
value to1
to fetch the first day of next month.
If you are following till now, you should have noticed that we are storing all dates for a month in daysInMonth
variable. Now, we will use this array of dates to render calendar component UI.
Before writing code for calendar component, let's first write a code to display calendar header where we will render names of days.
This is what we are going to render.
Here's a JSX that renders above UI.
First, we will need an array containing all days.
const days = ["sun", "mon", "tue", "wed", "thu", "fri", "sat"];
<div className="grid grid-cols-7 gap-6 sm:gap-12 place-items-center">
{
days.map((day, idx) => {
return (
<div key={idx} className="font-semibold">
{capitalizeFirstLetter(day)}
</div>
);
})}
</div>
Now that we have rendered calendar header, we can go ahead and write a code to display all dates in a grid format. We already have daysInMonth
that we need to display dates in a month.
<div className="grid grid-cols-7 gap-6 sm:gap-12 mt-8 place-items-center">
{daysInMonth.map((day, idx) => {
return (
<div key={idx} className={colStartClasses[getDay(day)]}>
<p
className={`cursor-pointer flex items-center justify-center font-semibold h-8 w-8 rounded-full hover:text-white ${
isSameMonth(day, today) ? "text-gray-900" : "text-gray-400"
} ${!isToday(day) && "hover:bg-blue-500"} ${
isToday(day) && "bg-red-500 text-white"
}`}
>
{format(day, "d")}
</p>
</div>
);
})}
</div>;
In above code,
-
isToday
function provided bydate-fns
is used to check if the given day is current day or not. -
isSameMonth
function provided bydate-fns
is used to check if the given day is a part of same month or not.
const colStartClasses = [
"",
"col-start-2",
"col-start-3",
"col-start-4",
"col-start-5",
"col-start-6",
"col-start-7",
];
By default, the first date of the month will be rendered in first column and first row. The above code will ensure that each date is getting rendered in the correct day column.
The above code will render following UI
As you can see, we are applying different classes based on conditionals to highlight current date and change text colors of other dates.
TL;DR
In this way, we can create a simple calendar component using Tailwind CSS and date-fns. You can check the final version of this code on my Github here.
Thank you for reading the article!
Posted on April 1, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.