Skip to content
Drivn
5 min read

React Calendar Component Examples

Drop-in React Calendar examples: single date, range, week numbers, year dropdown, and locale. Zero runtime UI deps — plain react-day-picker under Tailwind.

Scheduling UI is where date handling stops being abstract. A booking widget has to block sold-out days, an invoice editor needs a due date years out, a reporting filter needs a start and an end. Drivn's Calendar is the primitive underneath all three — a styled month grid over react-day-picker, installed as source into your repo by the Drivn CLI rather than imported from a package you cannot edit.

The examples on this page are the patterns that recur in shipped products: a controlled single-date field, the week-number and year-dropdown variants, two-click range selection, a non-English locale, and a disabled matcher that blocks past days. Each snippet imports from @/components/ui/calendar, the exact path npx drivn add calendar writes to, so nothing needs rewiring after you paste it in.

One decision to make before you start: inline grid or popover. Everything below renders the grid directly — inside a card, a sidebar, a booking panel — because that is how most scheduling surfaces present dates. If yours should open from a button showing a formatted value instead, reach for DatePicker, which composes this same Calendar behind a trigger. Otherwise take the example closest to your case and change the state binding.

Single-date selection

The default Calendar variant renders a month grid with the current month's days, navigated via left and right chevrons in the caption. Pass selected as a Date | undefined and onSelect as the setter — react-day-picker handles the day clicks and calls back with the clicked date or undefined when the user clicks the same day twice (the deselect gesture).

Because the Calendar source lives in your repo after install, adding custom styling or an extra disabled day is a local edit rather than a theme override. The snippet below is the minimum viable integration: a controlled useState<Date | undefined> bound to the component, with no wrapper div or extra state.

1import { useState } from "react"
2import { Calendar } from "@/components/ui/calendar"
3
4export default function Page() {
5 const [date, setDate] = useState<Date | undefined>()
6
7 return (
8 <Calendar
9 selected={date}
10 onSelect={setDate}
11 />
12 )
13}

Week numbers and dropdown variants

The variant prop switches between three visual shapes without changing any other behavior. variant="weekNumbers" adds an ISO week number column on the left, which payroll, invoicing, and scheduling tools often require. variant="dropdown" replaces the month/year caption with native select elements bounded by fromYear and toYear, which is the right default for birthday pickers, deadline inputs far from today, or any case where the user must jump across years without clicking through twelve months.

Both variants share the same selected, onSelect, and disabled props — you swap variant and nothing else changes. Under the hood, Drivn forwards showWeekNumber: true or captionLayout: 'dropdown' to react-day-picker, plus a few extra classNames that restyle the caption.

1<Calendar
2 variant="weekNumbers"
3 selected={date}
4 onSelect={setDate}
5/>
6
7<Calendar
8 variant="dropdown"
9 fromYear={1950}
10 toYear={2100}
11 selected={date}
12 onSelect={setDate}
13/>

Range selection

Calendar.Range is a compound child on the same root component — same import, same selected / onSelect contract, different type. selected is DateRange | undefined where DateRange = { from: Date; to?: Date } (Drivn re-exports the type from react-day-picker). The first click sets from; the second click sets to. Clicking a day outside the current range resets it and starts a new one.

The styling for mid-range days uses the bg-accent token so the highlight tracks your theme. When the range is a single day (from === to), Drivn skips the range styling so the calendar does not render a half-pill. For a range picker that opens from a trigger button instead of rendering inline, see DatePicker.Range.

1import { useState } from "react"
2import { Calendar, type DateRange } from "@/components/ui/calendar"
3
4export default function Page() {
5 const [range, setRange] = useState<DateRange | undefined>()
6
7 return (
8 <Calendar.Range
9 selected={range}
10 onSelect={setRange}
11 />
12 )
13}

Localization

Drivn re-exports the Locale type from react-day-picker, and you can pass any locale object to the locale prop. Weekday headers, month names, and the first day of the week all update — a Czech locale shifts the grid to start on Monday and renders "Led / Úno / Bře" at the top. No additional configuration is needed; Drivn forwards the locale object directly into DayPicker.

This means your i18n setup is the same one you already use for dates elsewhere in the app. Import a locale from react-day-picker/locale — dozens are pre-built — and pass it in. If your translation layer already knows what locale is active, wire it through a React context and the Calendar picks it up automatically. For tokens that affect the grid appearance, see theming.

1import { useState } from "react"
2import { Calendar } from "@/components/ui/calendar"
3import { cs } from "react-day-picker/locale"
4
5export default function Page() {
6 const [date, setDate] = useState<Date | undefined>()
7
8 return (
9 <Calendar
10 locale={cs}
11 selected={date}
12 onSelect={setDate}
13 />
14 )
15}

Disabling dates

The disabled prop takes a react-day-picker matcher and Drivn passes it straight through — a single Date, an array of them, a { from, to } range object, or a predicate shaped (date: Date) => boolean. Disabled days stop responding to clicks and render at text-muted-foreground/30 with a not-allowed cursor, which comes from the disabled key in the component's styles.classNames map. Nothing is translated on the way in, so any matcher from the react-day-picker docs works here verbatim.

The predicate form covers most real cases. Blocking past dates in a booking flow is (day) => day < today against a midnight-normalized reference — that is what the snippet below does, and without the setHours call today itself would be disabled for most of the day. Blocking weekends in a workday scheduler is (day) => [0, 6].includes(day.getDay()). Matchers run per rendered day and the grid only ever draws six weeks, so a dynamic cutoff costs nothing measurable. Pair the picker with an Input when the same form also needs a typed fallback.

1import { useState } from "react"
2import { Calendar } from "@/components/ui/calendar"
3
4export default function Page() {
5 const [date, setDate] = useState<Date | undefined>()
6 const today = new Date()
7 today.setHours(0, 0, 0, 0)
8
9 return (
10 <Calendar
11 selected={date}
12 onSelect={setDate}
13 disabled={(day) => day < today}
14 />
15 )
16}
Get started

Install Drivn in one command

Copy the source into your project and own every line. Zero runtime dependencies, pure React + Tailwind.

npx drivn@latest create

Requires Node 18+. Works with npm, pnpm, and yarn.

Enjoying Drivn?
Star the repo on GitHub to follow new component releases.
Star →

Frequently asked questions

No. The Calendar source imports DayPicker from react-day-picker and three chevron icons from lucide-react. That is the full runtime. There is no Radix, no cva, no clsx wrapper, no floating-ui — just the grid rendered and styled with Tailwind. If you already have lucide-react installed, the per-page cost after tree-shaking is react-day-picker plus a thin wrapper.

Yes — the default use case is inline. Render <Calendar> directly inside a card, a sidebar, or a booking panel and it shows as a regular block element. If you want it behind a trigger button with open/close logic, use the DatePicker component instead, which composes Calendar with the popover behavior and keyboard dismissal already wired.

Pass defaultMonth as a Date. The calendar opens to that month but leaves selected undefined so no day is highlighted. This is useful when you want to nudge the user toward a particular period without committing to a date — for example, opening a vacation booking picker on next month instead of the current one, or a quarterly report picker at the start of Q1.

Calendar renders the month grid inline and is a primitive you compose. DatePicker wraps Calendar in a trigger button plus a controlled popover, handling outside-click and Escape for you. If your UI shows the grid always (sidebar, card), use Calendar. If the grid should open from a button, use DatePicker — or build your own trigger and reuse Calendar inside it.

The Calendar file starts with "use client" because react-day-picker uses useState and event handlers. You can import it from a Server Component, but it renders on the client. If your page is a Server Component, leave the import where it is — Next.js draws the client boundary correctly based on the use client directive at the top of the file.