calendarcn
Calendar

Calendar Overview

Install CalendarCN, preview the main examples, and wire the calendar into controlled React state.

CalendarCN installs source into your app. The primary path is now primitive: CalendarRoot for the surface, CalendarToolbar for navigation, and optional add-ons for sheets, menus, and shortcut docs. CalendarScheduler remains available as the starter bundle when you want the current full CalendarCN behavior in one install.

Preview

Example Variants

Primitive starter

Compose `CalendarRoot` and `CalendarToolbar` directly when you want the smallest install surface and total source ownership.

Primitive rootSeparate toolbarCreate, move, resize

Mar 23 - 29, 2026

Time

Mon

Mar 23

Tue

Mar 24

Wed

Mar 25

Thu

Mar 26

Fri

Mar 27

Sat

Mar 28

Sun

Mar 29

All day

Use arrow keys to move the active time slot. Use left and right arrows to change days. Press Enter or Space to create an event at the active slot.

6 AM

7 AM

8 AM

9 AM

10 AM

11 AM

12 PM

1 PM

2 PM

3 PM

4 PM

5 PM

6 PM

7 PM

8 PM

9 PM

10 PM

Installation

Install the lean surface first. This gives you `CalendarRoot` plus the controlled toolbar, with the rest left open for composition.

Pull the selected CalendarCN bundle directly from the hosted item URLs. The primitive path installs separate open-code files; the starter path keeps the current composed scheduler.

terminal
npx shadcn@latest add \  https://calendarcn.phantomtechind.com/r/calendar-core.json \  https://calendarcn.phantomtechind.com/r/calendar-toolbar.json

Usage

app/schedule/page.tsx
"use client"import * as React from "react"import type {  CalendarClassNames,  CalendarCreateOperation,  CalendarEvent,  CalendarMoveOperation,  CalendarResizeOperation,  CalendarView,} from "@/components/calendar/types"import { CalendarRoot } from "@/components/calendar/root"import { CalendarToolbar } from "@/components/calendar/toolbar"import {  getRangeLabel,  applyMoveOperation,  applyResizeOperation,  createEventFromOperation,  shiftDate,} from "@/components/calendar/utils"const primitiveEventClassNames: CalendarClassNames = {  agendaEvent:    "data-[selected=true]:border-ring data-[selected=true]:ring-2 data-[selected=true]:ring-ring/60",  monthEvent:    "data-[selected=true]:border-ring data-[selected=true]:ring-2 data-[selected=true]:ring-ring/60",  timeGridEvent:    "data-[selected=true]:border-ring data-[selected=true]:ring-2 data-[selected=true]:ring-ring/60",}export function TeamSchedule() {  const [date, setDate] = React.useState(new Date())  const [events, setEvents] = React.useState<CalendarEvent[]>([])  const [selectedEventId, setSelectedEventId] = React.useState<string>()  const [view, setView] = React.useState<CalendarView>("week")  const currentLabel = React.useMemo(() => getRangeLabel(date, view), [date, view])  function handleCreate(operation: CalendarCreateOperation) {    setEvents((currentEvents) => [      ...currentEvents,      createEventFromOperation(operation, {        title: "New event",      }),    ])  }  function handleMove(operation: CalendarMoveOperation) {    setEvents((currentEvents) => applyMoveOperation(currentEvents, operation))  }  function handleResize(operation: CalendarResizeOperation) {    setEvents((currentEvents) => applyResizeOperation(currentEvents, operation))  }  return (    <div className="overflow-hidden rounded-[calc(var(--radius)*1.6)] border border-border/70">      <CalendarToolbar        activeResourceIds={[]}        availableViews={["month", "week", "day", "agenda"]}        currentLabel={currentLabel}        onNavigate={(direction) =>          setDate((currentDate) => shiftDate(currentDate, view, direction))        }        onToday={() => setDate(new Date())}        onViewChange={setView}        view={view}      />      <CalendarRoot        classNames={primitiveEventClassNames}        date={date}        events={events}        onEventCreate={handleCreate}        onEventMove={handleMove}        onEventResize={handleResize}        onSelectedEventChange={setSelectedEventId}        secondaryTimeZone="America/New_York"        selectedEventId={selectedEventId}        showSecondaryTimeZone        view={view}      />    </div>  )}

Examples

The calendar surface should document more than a single happy path. These versions cover both implementation patterns and the individual views that most teams need to wire up.

Implementation Patterns

Preset configurations that show how the same calendar surface adapts to different product constraints.

Calendar Views

Focused docs for each supported view so users can land directly on the surface they need to implement.

Accessibility

  • Week and day views expose a roving schedule grid. Tab enters the active day once, arrow keys move the active slot, and Enter or Space starts the create flow for that slot.
  • Event cards stay as buttons. Arrow keys move a selected event, Shift + Arrow resizes the end, Alt + Arrow resizes the start, and Shift + F10 opens the event context menu.
  • Month uses grid semantics and agenda uses list semantics so assistive technology gets the same structural cues as the visual layout.
  • The optional sheets, confirmation dialog, and shortcuts dialog add-ons all use modal focus management with escape handling, overlay dismiss, and focus restore.

Notes

  • CalendarRoot is fully controlled. Keep date, view, and events in your own state or wire them into your store.
  • CalendarToolbar is separate on purpose. Primitive installs keep navigation, view switching, and quick actions outside the surface component.
  • CalendarRoot exposes selected events through data-selected="true" on the event surface. Primitive installs should style selected state through classNames or CSS selectors, while CalendarScheduler already ships with starter defaults for selected events.
  • The utility helpers in @/components/calendar/utils are the fastest path to local optimistic handlers for move, resize, and create flows.
  • Use onEventCreateRequest, onEventMoveRequest, and onEventResizeRequest when your app wants to open a sheet or confirmation dialog before committing a change.
  • resources, businessHours, and blockedRanges are optional. The same core interaction model works with or without them.
  • CalendarScheduler is the starter bundle. Use it when you want the current composed toolbar, create/details sheets, confirmation dialog, and shortcuts dialog out of the box.
  • exportEventsToICS, parseICSText, and parseICSFile live in @/components/calendar/utils for import and export workflows outside the rendered calendar.

On this page