Reaching for a date picker in React often means reaching for a date library to power it, and historically that meant moment.js quietly adding a few hundred kilobytes to your bundle. React-Calendar sidesteps that whole tradition. It is an inline calendar component that lets users pick days, months, years, or entire decades, and it leans on the browser's native Intl API instead of a heavyweight date dependency. The result is a mature, accessible widget with a footprint so small it almost feels rude.
First published back in 2014 and maintained by Wojciech Maj (the same author behind react-pdf and react-date-picker), react-calendar has quietly become one of the most-downloaded date components in the ecosystem, pulling well over a million installs a week. It renders a calendar grid directly into your layout rather than hiding behind a text input, which makes it ideal for booking interfaces, availability views, dashboards, scheduling tools, and anywhere you want the calendar to be the centerpiece rather than a popover afterthought.
Why It Earns a Spot in Your Bundle
The headline feature is restraint. React-Calendar ships with only four runtime dependencies (@wojtekmaj/date-utils, clsx, get-user-locale, and warning), and none of them is a sprawling date library. Locale handling comes from the native Intl API, so you get correct month names, weekday labels, and week-start conventions for virtually any IETF language tag without bundling translation data yourself.
Beyond the small footprint, the component is genuinely capable:
- Multiple selection granularities — pick a single day, or drill up to choose a month, a year, or a decade.
- Range selection via
selectRange, returning a tidy[start, end]tuple. - Several calendar systems out of the box:
gregory,hebrew,islamic, andiso8601. - Deep customization of every tile through
tileClassName,tileContent, andtileDisabled. - Custom formatters for days, weekdays, months, and years.
- Min/max constraints and configurable detail levels via
minDetailandmaxDetail. - Display niceties like week numbers, double-month views, fixed six-week months, and neighboring-month days.
- Controlled and uncontrolled usage, with a
default*counterpart for every controlled prop. - Bundled TypeScript types and ARIA-labelled navigation for accessibility.
It is mature without being stale: the codebase is still actively maintained, and recent major versions added React 19 support.
Getting It Into Your Project
Install the package with your manager of choice:
npm install react-calendar
yarn add react-calendar
Then import the component and its default stylesheet. The CSS is optional in the sense that you can replace it entirely, but importing it first gives you a sensible baseline to override:
import Calendar from 'react-calendar';
import 'react-calendar/dist/Calendar.css';
One note for anyone on an older toolchain: as of version 6, the package is ESM-only. Modern bundlers like Vite, webpack 5, and Next handle this without a second thought, but if you have a Jest-based test setup that still expects CommonJS, you may need to add a little ESM configuration.
Your First Calendar
The minimal setup is a controlled component backed by useState. Because the value can be either a single date or a two-element range, the canonical type pattern looks like this:
import { useState } from 'react';
import Calendar from 'react-calendar';
import 'react-calendar/dist/Calendar.css';
type ValuePiece = Date | null;
type Value = ValuePiece | [ValuePiece, ValuePiece];
function DatePicker() {
const [value, setValue] = useState<Value>(new Date());
return (
<div>
<Calendar onChange={setValue} value={value} />
<p>Selected: {value instanceof Date ? value.toDateString() : 'none'}</p>
</div>
);
}
onChange fires when the user clicks a tile on the most detailed available view, which by default is a single day. The calendar handles all the navigation chrome for you: arrows to step between months, and clickable header labels to drill up to year, decade, and century views so users can jump across time quickly.
Selecting a Date Range
Booking flows and reporting filters usually want a start and an end. Flip on selectRange and onChange starts handing you a tuple instead of a single date:
function RangePicker() {
const [value, setValue] = useState<Value>([new Date(), new Date()]);
return (
<Calendar
onChange={setValue}
value={value}
selectRange={true}
/>
);
}
The first click sets the start, the second sets the end, and the highlighted band between them updates live. A few related props let you fine-tune the behavior: allowPartialRange emits onChange after the first click (handy if you want to react to a partially chosen range), and goToRangeStartOnSelect controls whether the view snaps back to the start once the end is picked.
Speaking the User's Language
Locale support is one of the quieter superpowers here. Pass a locale string and the calendar renders month names, weekday abbreviations, and the correct first day of the week for that region. You can also switch the underlying calendar system entirely:
<Calendar
locale="fr-FR"
calendarType="iso8601"
value={value}
onChange={setValue}
/>
Because this all flows through the native Intl API, you are not shipping locale data with the component. Switch to de-DE, ja-JP, or ar-SA and things simply render correctly. The calendarType prop accepts gregory, hebrew, islamic, and iso8601, with a sensible locale-dependent default when you leave it unset.
Pushing It Further
The basics get you a working picker in minutes. The more interesting work happens when you start decorating individual tiles and constraining what users can choose.
Marking Up Individual Days
Most real calendars need to communicate something about specific dates: this day has events, that day is fully booked, weekends look different. The tileClassName and tileContent props accept functions that receive each tile's context and return either a class name or React nodes.
const eventDates = new Set([
new Date(2026, 5, 21).toDateString(),
new Date(2026, 5, 25).toDateString(),
]);
<Calendar
value={value}
onChange={setValue}
tileClassName={({ date, view }) =>
view === 'month' && eventDates.has(date.toDateString())
? 'has-event'
: null
}
tileContent={({ date, view }) =>
view === 'month' && eventDates.has(date.toDateString())
? <span className="event-dot" aria-hidden />
: null
}
/>
Pair that with a little CSS, and you have the familiar "little dot under days with events" pattern that calendar apps rely on:
.has-event {
font-weight: 600;
}
.event-dot {
display: block;
width: 6px;
height: 6px;
margin: 2px auto 0;
border-radius: 50%;
background: #4f46e5;
}
The same callback shape powers tileDisabled, which decides whether a given tile can be clicked at all.
Constraining What Can Be Picked
A scheduling UI rarely wants to let users select arbitrary dates. The minDate and maxDate props clamp the selectable window, while tileDisabled lets you knock out specific days such as weekends or already-booked slots:
const today = new Date();
const inThirtyDays = new Date();
inThirtyDays.setDate(today.getDate() + 30);
<Calendar
value={value}
onChange={setValue}
minDate={today}
maxDate={inThirtyDays}
tileDisabled={({ date, view }) =>
view === 'month' && (date.getDay() === 0 || date.getDay() === 6)
}
/>
This example only allows weekdays within the next thirty days. You can also reach for minDetail and maxDetail to control how far users can drill: setting maxDetail="year", for instance, turns the component into a month picker that never descends to individual days.
Restyling Without a Fight
The default stylesheet is deliberately plain, on the assumption that most teams will want their own look. Every meaningful element carries a stable, predictable class name, which makes overriding straightforward:
.react-calendar {
border: none;
border-radius: 12px;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
font-family: inherit;
}
.react-calendar__tile--active {
background: #4f46e5;
border-radius: 8px;
}
.react-calendar__tile:hover {
border-radius: 8px;
}
Class names like react-calendar, react-calendar__tile, react-calendar__tile--active, and react-calendar__navigation are consistent across versions, so they play nicely with plain CSS, CSS Modules, or Tailwind's arbitrary-value targeting. You can also customize the navigation labels directly through props such as prevLabel, nextLabel, and navigationLabel if you would rather swap the arrows for icons.
Picking the Right Tool
React-Calendar is, by design, an inline calendar rather than a complete text-input picker. That is its sweet spot: when you want a visible calendar grid that drops in, restyles cleanly, and never drags a date library into your bundle, it is hard to beat. It is mature, well-typed, broadly localized, and genuinely small.
The trade-off is worth naming. If you specifically need a text field that opens a popover, or a built-in time picker, this component is not that out of the box. The same author's react-date-picker wraps this very engine in an input-plus-popover shell for exactly that case, and there are heavier all-in-one options if you live inside a particular design system. But for the common task of "show me a calendar, let me pick a date or a range, and let me make it look like my app," React-Calendar remains a clean, dependable, refreshingly lightweight answer.