Calendar View React component for Tailwind CSS v4
v1.0.0Added 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-viewCalendar 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) => voidFires 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.
defaultDateDateThe 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).
dateDateThe focused date (controlled); pair it with onDateChange.
onDateChange(date: Date) => voidFires 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-10 | 1 | 2 | 3 | 4 | 5 | 6 | -1The first day of the week: 0 is Sunday, 1 Monday ... 6 Saturday; -1 follows the locale (Sunday in en-US, Monday in de-DE).
dayStartHourdefault0numberThe first hour of the week and day grids.
dayEndHourdefault24numberThe hour the week and day grids end at (at least one hour after dayStartHour).
scrollToHourdefault8numberWhere the week and day grids open. Moving to another week keeps the scroll position you left.
slotMinutesdefault"30"15 | 30 | 60The snap for creating, moving and resizing events. Half-hour lines are dashed whenever the slot is under an hour.
hourHeightdefault48numberPixels per hour in the week and day grids. Touch screens get at least 56px so short events stay tappable.
heightdefault600numberHeight of the scrolling week / day grid and the most the agenda grows to before it scrolls, in px.
maxEventsPerDaydefault3numberChips per month cell before the last line turns into "+N more", which lists the day in a popover (a sheet on phones).
showWeekNumbersdefaultfalsebooleanISO week numbers in a narrow column at the start of each month row.
showNowIndicatordefaulttruebooleanA red line with a dot at the current time in today's column of the week and day grids; it moves once a minute.
hideWeekendsdefaultfalsebooleanThe week view shows the five working days of the locale.
editabledefaultfalsebooleanDrag 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>) => voidEvery click on an event. Call domEvent.preventDefault() to skip the built-in details (open your own drawer instead).
showEventDetailsdefaulttruebooleanThe built-in details on click: a popover next to the event, a bottom sheet on phones.
renderEventDetails(event: CalendarEvent) => ReactNodeReplaces the default details (title, time, location, description, attendees, meta) inside the popover or sheet.
openEventIdstring | nullThe event whose details are open (controlled), e.g. to open a new event right after onSlotSelect adds it.
defaultOpenEventIddefault""string | nullThe event whose details are open at first. In the playground try c26 (Implant follow-up on the 24th).
onOpenEventIdChange(id: string | null) => voidFires when details open or close.
onSlotSelect(slot: { start: Date; end: Date; allDay: boolean }) => voidMakes 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 }) => ReactNodeReplaces the content of chips and blocks; the button, position, tone and drag stay.
toolbardefaulttruebooleanThe title, prev / Today / next and the view switch.
toolbarActionsReactNodeButtons at the end of the toolbar, like "New appointment".
loadingdefaultfalsebooleanA thin indeterminate bar under the toolbar while events refresh; the current events stay.
emptyAgendaReactNodeShown when the agenda range has no events. Defaults to a small EmptyState with labels.noEvents.
agendaDaysdefault14numberHow many days the agenda view lists from the focused date; prev / next move by that many.
localedefault""stringMonth and weekday names, times (09:30 or 9:30 AM) and the first day of the week. Empty uses UIProvider, then the page language.
labelsPartial<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.
classNamesPartial<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.
childrenReactNodeCustom composition: the parts below in place of the built-in toolbar and body, sharing the calendar's state.
Parts
CalendarViewToolbar
componentThe toolbar: live-region title, prev / Today / next, the view switch (a phone switch below 576px) and actions. Props: { actions?: ReactNode } & ComponentProps<"div">.
CalendarMonth
componentThe month grid (role="grid") with lanes for chips and multi-day bars, "+N more" and keyboard roving. Takes div props.
CalendarMonthCompact
componentThe phone month: day buttons with up to three tone dots and the chosen day's events underneath. Takes div props.
CalendarWeek
componentThe 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
componentDays 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
componentThe 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
functionThe 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
functionThe 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
constantThe English strings, to spread under your own or translate once in UIProvider defaults. Type: CalendarViewLabels.
CalendarEvent
typeOne 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
typeWhat onEventChange receives after a move or resize: the new start, end and allDay.
CalendarSlotSelection
typeWhat 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
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
Used in
More React data display components
Articles using this component
- React Calendar Component with Events: Month, Week, Day
Component guides10 min read
- React Resource Scheduler: Drag to Book Rooms and Cars
Component guides10 min read