Skip to content
WingoUI
ComponentsTemplatesDocsPricingAI agentsBlogChangelogContact
Components
Buttons & Actions
ButtonButton GroupCopy ButtonDelete ButtonTheme ToggleToggleToggle Group
Inputs
Avatar UploadCalendarCheckboxColor PickerComboboxCountry SelectCron PickerCurrency InputDate PickerDate Range PickerDate Time PickerDuration PickerEmail InputEmoji PickerFile UploadFilter BuilderInputInput GroupListboxMasked InputMention InputMonth PickerMulti SelectNumber InputOTP InputPassword InputPhone InputRadio GroupRatingSearch InputSegmented ControlSelectSignature PadSliderSwitchTag InputTextareaTime PickerTimezone SelectTree Select
Forms
Address InputBooking WidgetCSV ImportFieldForm WizardInline EditInvoice line itemsRich Text EditorSchema Builder
Navigation
Back To TopBounce SidebarBreadcrumbCommand MenuGlobal SearchGooey NavHook SidebarNav MenuNav TabsPaginationProximity SidebarScroll ProgressSidebarStepsTable Of ContentsTabsTopbarUser MenuWorkspace Switcher
Overlays
Alert DialogContext MenuDialogDrawerDropdown MenuFamily DrawerHover CardPopoverShare DialogSheetShortcuts DialogTooltipTour
Feedback
AlertBannerCookie ConsentEmoji ReactionEmpty StateNotification BellNotification CenterProgressRoute ProgressSetup ChecklistSkeletonSpinnerToast
Data Display
Approval FlowAvatarBadgeCalendar ViewCode BlockComment ThreadContact cardCountdownDescription ListDiff ViewerFile ListFile managerFolderGanttJSON ViewerKbdLog ViewerMarkdownPresenceSchedulerTagTimelineTree View
Tables & Lists
Audit LogData TableDeal PipelineEditable GridKanbanListMembers tablePermissions MatrixTableTask ListVirtual List
Charts & Stats
Animated CounterArea chartBar chartDonut chartFunnel chartGaugeGitHub ActivityHeatmap calendarLeaderboardLine chartRadar chartRadial barSparklineStat CardTrackerTrend badge
Layout
AccordionApp ShellCardCollapsibleDetail LayoutEntity HeaderPage HeaderScroll AreaSeparatorSettings LayoutSplit ViewToolbar
Media
Audio PlayerCarouselCSV ViewerDOCX EditorDOCX ViewerExcel ViewerFile SystemImage compareImage CropperImage GalleryImage LightboxLocation MapPDF ViewerPowerPoint ViewerQR CodeStep PlayerVideo Player
AI Kit
Agent Status CardChat MessageChat ThreadChat WidgetConversation ListFluid OrbGrid RevealMatrix OrbModel PickerPrompt InputReasoning IndicatorRule BuilderSuggestion ChipsTool Call TimelineUsage MeterVoice InputWorkflow Canvas
Text & Effects
Background PatternGravity LettersMarqueeTerminalText AnimateText Rotate
Mobile
Action SheetBottom Tab BarFABHaptic ButtonHero HeaderInstall PromptLarge Title HeaderNotification BannerOnboarding CarouselPicker WheelPull To RefreshSlide To ConfirmSticky CTA BarSwipe DeckSwipeable Tabs
Commerce
Cart drawerCategory pickerCheckout stepsCoupon inputFavorite buttonFilter panelListing cardListing gridOrder trackerPricePrice rangePricing tableProduct galleryQuantity stepperReviewsSeller card
Marketing Sections
App Download SectionBlog Cards SectionCTA SectionFAQ SectionFeature GridFeature ShowcaseHero SectionIntegrations SectionLogo CloudMarketing FooterMarketing HeaderMarketing SectionNewsletter SectionPricing SectionStats SectionSteps SectionTeam SectionTestimonials SectionTimeline SectionVideo Section
Blocks
Admin Dashboard PageAdmin ShellAI Chat PageAPI Keys PageAutomation Builder PageCheckout PageCRM Contact PageCRM Pipeline PageError PagesInbox PageInvoice PageKPI DashboardListing Detail PageLogin PageMarketplace Home PageMarketplace Search PageMarketplace ShellMobile App HomeOnboarding FlowOrder History PagePost Listing WizardPricing PageProfile PageRegister PageSettings PageUsers Admin Page
Templates
About PageAcademy PageAccessibility PageAffiliate PageAgency PageAI Product PageAppointment Booking PageBlog PageBlog Post PageBug Bounty PageCareers PageChangelog PageCommunity PageCompare PageContact PageCustomer Story PageCustomers PageDemo Booking PageDevelopers PageDocs Home PageDownload PageEnterprise PageEvents PageExperts PageFAQ PageFeature PageGlossary PageHelp Article PageHelp Center PageHome Services PageImpact PageIntegration Detail PageIntegrations PageJob Detail PageLanding PageLaunch PageLegal PageManifesto PagePortfolio PagePress PageProduct Tour PagePromo PageReal Estate PageReferral PageResearch Report PageRestaurant PageRoadmap PageROI Calculator PageSecurity PageSolutions PageStartups PageStatus PageStorefront PageSwitch PageTemplate Gallery PageTravel PageWaitlist PageYear in Review Page
Hooks & Utilities
AI ChatChart kitCommerceDate utilsFile exportFile typesFiltersForm validationHapticsInvoicingMotion presetsNavigationNumber formatRelative timeRomanian locationsSpreadsheetText matchTonesUIProvideruseClipboarduseConsentuseControllableStateuseCooldownuseDebounceuseKeyboardInsetuseLocaleuseLongPressuseMediaControlsuseMediaQueryuseOverflowItemsusePersistedStateuseScrollEdgesuseScrollStateuseSelectionuseShortcutuseStickToBottom
GalleryTheme

Inputs

Calendar

Inputs

Calendar React component for Tailwind CSS v4

Avatar UploadCheckbox
Live props
Behavior
mode
pagedNavigation
monthYearPicker
minDaysNo minimum
maxDaysNo limit
excludeDisabled
maxSelectedNo limit
todayButton
autoFocus
name
Layout
numberOfMonths1
showOutsideDays
fixedWeeks
showWeekNumbers
Content
locale
weekStartsOn-1
Appearance
size
tone
color
radius
strikeDisabled
Code
import { Calendar } from "@/components/ui/calendar"

<Calendar />
  1. Home
  2. Components
  3. Inputs
  4. Calendar
Pro

Calendar React component for Tailwind CSS v4

v1.0.0·Added in 1.0.0

A React calendar to pick one day, several days or a range, with disabled days, event dots, several months and a quick month / year jump.

Click a day to pick it; in multiple mode each click adds or removes a day, and in range mode the first click sets the start, the band follows the pointer as a preview, and the second click sets the end (a day before the start swaps them). The arrows move a month (or a page with pagedNavigation) and the grid slides the way you went. Click the caption for a months grid, click again for years, pick one to zoom back in. Keyboard: Tab lands on one day (the selected, else today); arrows move a day or a week, Home / End jump to the week's ends, PageUp / PageDown move a month and Shift+PageUp / PageDown a year, Enter or Space picks; in the months and years grids arrows move, Enter picks and Escape returns to the days. On a phone the cells grow to 44px and a swipe left or right changes the month; vertical scrolling still works.

Calendar is a Pro React component in the React input components category, written in TypeScript for React 19 and Tailwind CSS v4. It installs as source with npx wingo-ui@latest add calendar, together with Button, Date utils, useControllableState, useLocale, useMediaQuery and 3 more and the npm packages date-fns, motion and lucide-react.

Add to a project

Requirements: React 19 and Tailwind CSS v4, in Next.js (App Router or Pages), Vite or React Router. Installation guide · Next.js setup · Vite setup

npx wingo-ui@latest add calendar

Calendar is part of Pro, with 298 more Pro items: $8 a month or $150 once. 14-day refund before you download any Pro item. See plans

Usage

Import Calendar from @/components/ui/calendar.

import { useState } from "react"
import { Calendar, type DateRange } from "@/components/ui/calendar"

const booked = [new Date(2026, 9, 9), new Date(2026, 9, 10), new Date(2026, 9, 11)]

export function StayPicker() {
  const [stay, setStay] = useState<DateRange>({ from: null, to: null })
  return (
    <Calendar
      mode="range"
      value={stay}
      onValueChange={setStay}
      min={new Date()}
      disabled={booked}
      minDays={3}
      strikeDisabled
      events={[{ date: new Date(2026, 9, 14), tone: "warning", label: "Request pending" }]}
      labels={{ today: "Back to today", selected: "picked" }}
    />
  )
}

Props

  • modedefault "single"
    "single" | "multiple" | "range"

    What a click picks: one day, a set of days (each click adds or removes one), or a range (start, then end). The value type follows the mode: Date | null, Date[] or { from, to }.

  • value
    Date | null | Date[] | DateRange

    The selection, controlled. Typed by mode: Date | null for single, Date[] for multiple, { from: Date | null; to: Date | null } for range. A value outside min / max shows as given; only clicks are limited.

  • defaultValue
    Date | null | Date[] | DateRange

    The starting selection while uncontrolled: null, [] or { from: null, to: null } by mode.

  • onValueChange
    (value: Date | null | Date[] | DateRange) => void

    Called on every pick with the next value (typed by mode). In single mode a new day keeps the time of the previous value, so a date-time picker never loses its time.

  • month
    Date

    The first visible month, controlled. Pair it with onMonthChange.

  • defaultMonth
    Date

    The first visible month on mount. Defaults to the value's month, else today's, and is clamped to min / max.

  • onMonthChange
    (month: Date) => void

    Called with the first day of the new first visible month after an arrow, a swipe, a keyboard move or a jump.

  • numberOfMonthsdefault 1
    number

    Months side by side from 640px wide; below that they stack, each with its own caption. In a narrow container on a wide screen, stack them with classNames={{ months: "sm:flex-col" }} (or a container query).

  • pagedNavigationdefault false
    boolean

    The arrows and swipes move by numberOfMonths instead of one month.

  • min
    Date

    Earlier days are disabled and the previous arrow stops at this month.

  • max
    Date

    Later days are disabled and the next arrow stops at this month. min after max leaves nothing enabled (a dev warning says so).

  • disabled
    DateMatcher or DateMatcher[] (Date, Date[], { from, to }, { before }, { after }, { dayOfWeek } or (date) => boolean)

    Days that can not be picked: a Date, a list, { from, to }, { before }, { after }, { dayOfWeek: [0, 6] } for weekends, or a (date) => boolean test. Disabled days stay focusable (aria-disabled) so the keyboard can pass over them.

  • localedefault ""
    string

    Month and weekday names, digits and the first day of the week, all from Intl. Empty uses the nearest LocaleProvider, else the page's language. en-US starts on Sunday, de-DE on Monday.

  • weekStartsOndefault -1
    0 | 1 | 2 | 3 | 4 | 5 | 6 | -1

    The first day of the week: 0 is Sunday, 1 Monday ... 6 Saturday; -1 follows the locale (Sunday in en-US, Monday in de-DE).

  • showOutsideDaysdefault true
    boolean

    Shows the neighbour months' days that fill the first and last week, dimmed. They can be picked and move the view there.

  • fixedWeeksdefault false
    boolean

    Always six weeks, so the grid never changes height between months (pickers turn it on). Without it the height change animates.

  • showWeekNumbersdefault false
    boolean

    A column of week numbers: ISO weeks when the week starts on Monday, the locale's numbering otherwise. On touch screens md days take 40px instead of 44px to make room, so it still fits a 360px phone.

  • events
    { date: Date; tone?: Tone; color?: string; label?: string }[]

    Dots under a day (up to 3): bookings, deadlines, holidays. tone or any color; labels are read out with the day. On a selected day the dots turn the selection's text color.

  • monthYearPickerdefault true
    boolean

    The caption becomes a button: first a months grid, then a years grid (20 at a time), for fast jumps to a birthday or next year. Off, the caption is plain text.

  • minDaysdefault 0
    number

    Range mode: the shortest range in days, both ends counted (a 2 night minimum stay is 3). While picking the end, closer days are unavailable; clicking the start again then clears it. 0 = no minimum.

  • maxDaysdefault 0
    number

    Range mode: the longest range in days, both ends counted. Farther days are unavailable while picking the end. 0 = no limit.

  • excludeDisableddefault true
    boolean

    Range mode: a range can not span a disabled day. While picking the end, every day past the first disabled one on each side is unavailable (booked nights in a stay).

  • maxSelecteddefault 0
    number

    Multiple mode: how many days can be picked. At the limit the other days are unavailable until one is removed. 0 = no limit.

  • sizedefault "md"
    "sm" | "md" | "lg"

    Day cells of 32, 36 or 44px. On touch screens md grows to 44px, so seven columns still fit a 360px phone.

  • tonedefault "neutral"
    "neutral" | "brand" | "success" | "warning" | "danger" | "info"

    The selection color: the day fill, the range band and the months / years highlight.

  • colordefault ""
    string

    Any CSS color for the selection; replaces the tone. Text on the fill turns black or white by lightness and the band is a tint of it.

  • radiusdefault "full"
    "md" | "full"

    The day shape: circles (the default, like iOS) or rounded squares. The range band rounds to match.

  • strikeDisableddefault false
    boolean

    Strikes through disabled days, for booked nights and sold-out dates.

  • todayButtondefault false
    boolean

    A Today button under the grid that jumps back to the current month (and closes the months / years view).

  • labels
    Partial<{ previousMonth; nextMonth; previousYear; nextYear; previousYears; nextYears; chooseMonth; chooseYear; today; weekNumber; selected; event: string }>

    Every visible and spoken string, merged over the English defaults ("Previous month", "Today", "selected"...). Pass translated ones in another language.

  • renderDay
    (date: Date, state: { today; selected; rangeStart; rangeMiddle; rangeEnd; outside; disabled; hoverRange; events }) => ReactNode

    The content inside a day button, in place of the number: a price under each day, a badge. Use size lg for two lines.

  • footer
    ReactNode

    Content under the grid: a range summary, a time field, actions.

  • autoFocusdefault false
    boolean

    Focuses the selected day (else today) on mount. Pickers pass it when they open from the keyboard.

  • namedefault ""
    string

    Renders hidden inputs for native forms, as local ISO dates ("2026-09-23"): one for single, one per day for multiple, name.from and name.to for a range. A form reset returns to defaultValue. The demo prints what the form would submit.

  • classNames
    Partial<Record<"header" | "caption" | "nav" | "months" | "grid" | "weekday" | "day" | "today" | "selected" | "range" | "outside" | "disabled" | "events" | "view" | "footer", string>>

    Extra classes for the inner parts. range goes on the cells that carry the band, the day keys on the day buttons.

  • className
    string

    Extra classes for the root. Override the tone vars here, e.g. [--tone-soft:...] for a custom band.

Examples

Booking a stay

Range mode with booked nights struck through, a 2 night minimum (minDays 3) and the total in the footer.

Several days

multiple with maxSelected 3 and weekends disabled; at the limit the other days wait until one is removed.

Two months, paged

numberOfMonths 2 with pagedNavigation: the arrows move both months. They stack below 640px; here a container query on classNames.months also stacks them in a narrow panel.

Prices under each day

renderDay puts a nightly price under the number; size lg leaves room for two lines.

Locales

Names, digits and the first day of the week come from Intl: Sunday in en-US, Monday in ro-RO and de-DE.

Birthday

max is today; click the caption to jump by month, then by year. Today brings you back.

Sizes, shapes, week numbers

32px cells with rounded squares and ISO week numbers, next to 44px circles.

Custom color and events

Any CSS color for the selection, and up to three dots a day from tones or colors.

Dependencies

date-fnsmotionlucide-reactbuttondate-utilsuse-controllable-stateuse-localeuse-media-querymotiontonesui-config

Files

  • components/ui/calendar.tsx

FAQ

Is the React Calendar component free?

The preview, props and docs are free; copying the source needs a Pro plan. See plans.

How do I install Calendar?

Run npx wingo-ui@latest add calendar. It also adds the Wingo UI items it builds on (Button, Date utils, useControllableState, useLocale, useMediaQuery and 3 more) and the npm packages date-fns, motion and lucide-react. With shadcn, add the @wingo-ui registry with your API key, then run npx shadcn@latest add @wingo-ui/calendar.

How does Calendar behave on phones?

On a phone the cells grow to 44px and a swipe left or right changes the month; vertical scrolling still works.

See also

Date PickerDate Range PickerTime PickerDate utilslibuseLocalehook

Used in

Date Range PickerDate PickerDate Time PickerFilter BuilderDemo Booking Page

More React input components

SliderSwitchMasked InputSegmented ControlCheckboxCombobox
All 40 React input components

Articles using this component

  • React Appointment Booking That Gets Time Zones Right

    Build it: screens and apps · Nov 6, 2026 · 9 min read

  • Shadcn Date Time Picker with Time Zones and Presets

    Component guides · Nov 5, 2026 · 9 min read

  • React Calendar Component with Events: Month, Week, Day

    Component guides · Oct 19, 2026 · 10 min read

  • React Date Range Picker With Presets and a Phone Sheet

    Component guides · Oct 13, 2026 · 9 min read

PreviousAvatar UploadNext Checkbox

Availability

Cabin in Aspen

$180

/ night

SuMoTuWeThFrSa
November 2026
ConfirmedPending12Unavailable

Friday, November 20

Free, check-in from 3:00 PM

$180