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

Data Display

Calendar View

Data Display

Calendar View React component for Tailwind CSS v4

BadgeCode Block
Live props
State
defaultView
defaultOpenEventId
loading
Layout
mobileView
layout
weekStartsOn-1
dayStartHour0
dayEndHour24
scrollToHour8
hourHeight48px
height600px
maxEventsPerDay3
hideWeekends
toolbar
Appearance
variant
tone
showWeekNumbers
showNowIndicator
Behavior
slotMinutes
editable
showEventDetails
agendaDays14
Content
locale
Code
import { CalendarView } from "@/components/ui/calendar-view"

<CalendarView />
  1. Home
  2. Components
  3. Data Display
  4. Calendar View
Pro

Calendar View React component for Tailwind CSS v4

v1.0.0·Added in 1.0.0

A React event calendar for appointments and meetings: month, week, day and agenda views, a now line and drag to reschedule.

Switch between Month, Week, Day and Agenda in the toolbar; the arrows move a period back or forward and Today jumps home, while the body slides in the direction you went and the title announces the new range. Click or tap an event to open its details (time, location, attendees) in a popover next to it; on a phone they rise as a bottom sheet. A month cell that has more events than fit shows "+2 more", which lists the whole day. With editable on and a mouse or pen, drag an event to another time or day (it snaps to the slot and springs into place while the original stays as a faint ghost) and drag its bottom edge to change the length; the change is applied at once and undone if onEventChange returns false. With onSlotSelect, click an empty slot to pick an hour or drag down the grid (or across days in the month) to select a range. Click a day number or a week column heading to open that day. Keyboard: the month is one Tab stop; arrows move by day and week, Home / End go to the start and end of the week, PageUp / PageDown change the month (Shift for a year), Enter opens the day's events, and Tab moves on to the events inside the day. In week, day and agenda PageUp / PageDown change the period while focus is inside, and events are buttons in time order read as the title and the time range. Escape closes the details and returns focus to the event. On a phone (or whenever the calendar is narrower than 576px) month and week turn into the agenda (or the compact month with dots and the day's list below, or the day view), agenda days have sticky headings and open at today, rows are 56px tall, and you swipe left or right on the day view and the compact month to change the period. Dragging events is off on touch so the page still scrolls. Reduced motion replaces the slides with fades.

Calendar View is a Pro React component in the React data display components category, written in TypeScript for React 19 and Tailwind CSS v4. It installs as source with npx wingo-ui@latest add calendar-view, together with Avatar, Button, Empty State, Popover, Progress and 9 more and the npm packages @dnd-kit/core, date-fns, lucide-react and motion.

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-view

Calendar View 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 CalendarView from @/components/ui/calendar-view.

import { useState } from "react"
import { Plus } from "lucide-react"
import { Button } from "@/components/ui/button"
import { CalendarView, type CalendarEvent } from "@/components/ui/calendar-view"

const appointments: CalendarEvent[] = [
  { id: "p1", title: "Consultation: Emma Carter", start: new Date(2026, 8, 24, 9, 0), end: new Date(2026, 8, 24, 10, 0), tone: "info", location: "Room 1" },
  { id: "p2", title: "Cleaning: Irene Vance", start: new Date(2026, 8, 24, 11, 30), end: new Date(2026, 8, 24, 12, 30), tone: "success" },
  { id: "p3", title: "Supplies delivery (UPS)", start: new Date(2026, 8, 24), end: new Date(2026, 8, 24), allDay: true },
]

export function Appointments() {
  const [events, setEvents] = useState(appointments)
  return (
    <CalendarView
      events={events}
      defaultDate={new Date(2026, 8, 24)}
      editable
      onEventChange={(event, change) =>
        setEvents((list) => list.map((e) => (e.id === event.id ? { ...e, ...change } : e)))
      }
      toolbarActions={<Button size="sm" leftIcon={<Plus />}>New appointment</Button>}
      labels={{
        more: (n) => `${n} more`,
        noEvents: "No appointments",
      }}
    />
  )
}

Props

  • eventsdefault "[]"
    CalendarEvent[]

    The events: { id, title, start: Date, end: Date, allDay?, tone?, color?, location?, description?, attendees?: { name, avatar? }[], editable?, meta? }. An all-day event ending at local midnight ends the day before; an end at or before the start reads as 30 minutes (with a dev warning). Events crossing midnight show a block per day; multi-day events span the month cells as one bar per week.

  • defaultViewdefault "month"
    "month" | "week" | "day" | "agenda"

    The view it opens in. Control it with view and onViewChange.

  • view
    "month" | "week" | "day" | "agenda"

    The current view (controlled); pair it with onViewChange.

  • onViewChange
    (view: CalendarViewMode) => void

    Fires when the switch, a day number or a week column heading changes the view.

  • viewsdefault "["month", "week", "day", "agenda"]"
    CalendarViewMode[]

    The views in the switch, in this order. A single view hides the switch.

  • defaultDate
    Date

    The focused date it opens on. Without it the server renders the first event's day and the browser moves to today after mount (no hydration mismatch).

  • date
    Date

    The focused date (controlled); pair it with onDateChange.

  • onDateChange
    (date: Date) => void

    Fires on prev / next / Today, on arrow keys in the month and when a day is picked.

  • mobileViewdefault "agenda"
    "agenda" | "day" | "month"

    What phones get instead of month and week (a container query: whenever the calendar is narrower than 576px, so a narrow side panel gets it too; both layouts are in the server HTML): the agenda of that month or week, the focused day, or a compact month with dots and the chosen day's events listed underneath. The phone switch only offers views that make sense there.

  • layoutdefault "responsive"
    "responsive" | "desktop" | "mobile"

    responsive switches to the phone layout when the calendar itself is narrower than 576px; desktop and mobile force one layout, e.g. a phone frame on a wide screen.

  • variantdefault "surface"
    "surface" | "outline" | "plain"

    surface: a raised panel. outline: a hairline border on the page. plain: no container, for a page that is only the calendar.

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

    The tone of events without their own tone or color: chips, blocks, agenda bars and dots.

  • 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).

  • dayStartHourdefault 0
    number

    The first hour of the week and day grids.

  • dayEndHourdefault 24
    number

    The hour the week and day grids end at (at least one hour after dayStartHour).

  • scrollToHourdefault 8
    number

    Where the week and day grids open. Moving to another week keeps the scroll position you left.

  • slotMinutesdefault "30"
    15 | 30 | 60

    The snap for creating, moving and resizing events. Half-hour lines are dashed whenever the slot is under an hour.

  • hourHeightdefault 48
    number

    Pixels per hour in the week and day grids. Touch screens get at least 56px so short events stay tappable.

  • heightdefault 600
    number

    Height of the scrolling week / day grid and the most the agenda grows to before it scrolls, in px.

  • maxEventsPerDaydefault 3
    number

    Chips per month cell before the last line turns into "+N more", which lists the day in a popover (a sheet on phones).

  • showWeekNumbersdefault false
    boolean

    ISO week numbers in a narrow column at the start of each month row.

  • showNowIndicatordefault true
    boolean

    A red line with a dot at the current time in today's column of the week and day grids; it moves once a minute.

  • hideWeekendsdefault false
    boolean

    The week view shows the five working days of the locale.

  • editabledefault false
    boolean

    Drag events to move them and drag their bottom edge to resize, with a mouse or pen, snapped to slotMinutes. Events can opt out with editable: false. Off on touch screens, where dragging would fight scrolling.

  • onEventChange
    (event: CalendarEvent, change: CalendarEventChange) => (false | void)

    After a move or resize. The change shows at once; return false to put the event back. Update your events with the new times.

  • onEventClick
    (event: CalendarEvent, domEvent: MouseEvent<HTMLElement>) => void

    Every click on an event. Call domEvent.preventDefault() to skip the built-in details (open your own drawer instead).

  • showEventDetailsdefault true
    boolean

    The built-in details on click: a popover next to the event, a bottom sheet on phones.

  • renderEventDetails
    (event: CalendarEvent) => ReactNode

    Replaces the default details (title, time, location, description, attendees, meta) inside the popover or sheet.

  • openEventId
    string | null

    The event whose details are open (controlled), e.g. to open a new event right after onSlotSelect adds it.

  • defaultOpenEventIddefault ""
    string | null

    The event whose details are open at first. In the playground try c26 (Implant follow-up on the 24th).

  • onOpenEventIdChange
    (id: string | null) => void

    Fires when details open or close.

  • onSlotSelect
    (slot: { start: Date; end: Date; allDay: boolean }) => void

    Makes empty time selectable: click a slot for an hour, or drag down the grid (desktop) with a ghost "New event" showing the range; in the month click a day or drag across days for all-day ranges. Tap on touch.

  • renderEvent
    (event: CalendarEvent, info: { view: CalendarViewMode }) => ReactNode

    Replaces the content of chips and blocks; the button, position, tone and drag stay.

  • toolbardefault true
    boolean

    The title, prev / Today / next and the view switch.

  • toolbarActions
    ReactNode

    Buttons at the end of the toolbar, like "New appointment".

  • loadingdefault false
    boolean

    A thin indeterminate bar under the toolbar while events refresh; the current events stay.

  • emptyAgenda
    ReactNode

    Shown when the agenda range has no events. Defaults to a small EmptyState with labels.noEvents.

  • agendaDaysdefault 14
    number

    How many days the agenda view lists from the focused date; prev / next move by that many.

  • localedefault ""
    string

    Month and weekday names, times (09:30 or 9:30 AM) and the first day of the week. Empty uses UIProvider, then the page language.

  • labels
    Partial<CalendarViewLabels>

    Every string, English by default: today, previous, next, month, week, day, agenda, allDay, more(n), noEvents, newEvent, weekNumber, eventTime(start, end), eventCount(n), views, loading, drag { start, end, cancel }, details { location, attendees, close, title }. DEFAULT_CALENDAR_VIEW_LABELS has them all.

  • classNames
    Partial<Record<"toolbar" | "title" | "nav" | "switch" | "actions" | "body" | "month" | "weekdays" | "day" | "week" | "event" | "more" | "weekHeader" | "allDay" | "scroll" | "gutter" | "grid" | "now" | "agenda" | "agendaDay" | "agendaEvent" | "details", string>>

    Extra classes for the inner parts.

  • children
    ReactNode

    Custom composition: the parts below in place of the built-in toolbar and body, sharing the calendar's state.

Parts

  • CalendarViewToolbar

    component

    The toolbar: live-region title, prev / Today / next, the view switch (a phone switch below 576px) and actions. Props: { actions?: ReactNode } & ComponentProps<"div">.

  • CalendarMonth

    component

    The month grid (role="grid") with lanes for chips and multi-day bars, "+N more" and keyboard roving. Takes div props.

  • CalendarMonthCompact

    component

    The phone month: day buttons with up to three tone dots and the chosen day's events underneath. Takes div props.

  • CalendarWeek

    component

    The time grid: day headings, the all-day row, the hour gutter, overlapping blocks, the now line and the selection ghost. Props: { mode?: "week" | "day" } & ComponentProps<"div">.

  • CalendarAgenda

    component

    Days with events as sticky headings over 56px rows (time, tone bar, title, location); scrolls to today on mount. Props: { range?: { start: Date; end: Date } } & ComponentProps<"div">.

  • CalendarEventChip

    component

    The month chip on its own: a tinted button with a 3px start bar in the event's tone. Props: { event: CalendarEvent; time?: ReactNode; tone?: Tone } & ComponentProps<"button">.

  • layoutDayEvents

    function

    The overlap layout of one day: segments with start / end minutes and column, columns and span (greedy columns, each event stretching over the free columns to its right). Signature: (events: CalendarEvent[], day: Date) => CalendarDayLayout[].

  • getVisibleRange

    function

    The first and last visible day of a view, both inclusive local midnights. Use it to fetch events for the screen. Signature: (view, date, weekStartsOn?, agendaDays?) => { start: Date; end: Date }.

  • DEFAULT_CALENDAR_VIEW_LABELS

    constant

    The English strings, to spread under your own or translate once in UIProvider defaults. Type: CalendarViewLabels.

  • CalendarEvent

    type

    One event: id, title, start and end (Dates; an all-day end at midnight is exclusive), allDay, tone or color, location, description, attendees ({ name, avatar }), editable (false keeps it still) and meta for the details.

  • CalendarEventChange

    type

    What onEventChange receives after a move or resize: the new start, end and allDay.

  • CalendarSlotSelection

    type

    What onSlotSelect receives: the selected start, end and allDay.

  • CalendarViewMode

    type

    "month" | "week" | "day" | "agenda".

Examples

Sales week

Week and day only, weekends hidden, 15-minute snapping, drag to reschedule.

Agenda only

One view hides the switch: a phone-sized list of scheduled deliveries with sticky days.

Busy month

Three chips per day then "+N more", a multi-day bar and ISO week numbers.

Select a range to create

onSlotSelect adds the event and openEventId opens its details right away.

Day view with overlaps

Three parallel appointments share the column; the lunch break is locked with editable: false.

Phone: compact month

layout="mobile" with mobileView="month": dots per day, the day's list below, swipe for the next month.

Dependencies

@dnd-kit/coredate-fnslucide-reactmotionavatarbuttonempty-statepopoverprogresssegmented-controluse-controllable-stateuse-localeuse-media-querydate-utilsmotionrelative-timetonesui-config

Files

  • components/ui/calendar-view.tsx

FAQ

Is the React Calendar View component free?

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

How do I install Calendar View?

Run npx wingo-ui@latest add calendar-view. It also adds the Wingo UI items it builds on (Avatar, Button, Empty State, Popover, Progress and 9 more) and the npm packages @dnd-kit/core, date-fns, lucide-react and motion. With shadcn, add the @wingo-ui registry with your API key, then run npx shadcn@latest add @wingo-ui/calendar-view.

How does Calendar View behave on phones?

Click or tap an event to open its details (time, location, attendees) in a popover next to it; on a phone they rise as a bottom sheet.

See also

CalendarSchedulerDate PickerTimelinePopoverDrawer

Used in

Scheduler

More React data display components

Code BlockComment ThreadContact cardDescription ListDiff ViewerFile List
All 23 React data display components

Articles using this component

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

    Component guides · Oct 19, 2026 · 10 min read

  • React Resource Scheduler: Drag to Book Rooms and Cars

    Component guides · Oct 16, 2026 · 10 min read

PreviousBadgeNext Code Block

September 2026

Su
Mo
Tu
We
Th
Fr
Sa
  1. Tuesday, September 1

  2. Wednesday, September 2

  3. Thursday, September 3

  4. Friday, September 4

  5. Monday, September 7

  6. Tuesday, September 8

  7. Wednesday, September 9

  8. Thursday, September 10

  9. Friday, September 11

  10. Monday, September 14

  11. Tuesday, September 15

  12. Wednesday, September 16

  13. Thursday, September 17

  14. Friday, September 18

  15. Saturday, September 19

  16. Monday, September 21

  17. Tuesday, September 22

  18. Wednesday, September 23

  19. Thursday, September 24

  20. Friday, September 25

  21. Saturday, September 26

  22. Monday, September 28

  23. Tuesday, September 29

  24. Wednesday, September 30

grid 00–24 h · 30-min slots · 48 px/h · opens at 08:00 · agenda 14 d · weekends shown · now line on · drag off · details on · open none
Click an empty slot to add an appointment; turn on editable to drag events.