Skip to content
WANCRAFT

G.207Data & Feedback

Event Calendar

A month and week calendar with colour-coded event chips, "+N more" popovers, timed blocks, direction-aware slides and grid keys.

NewReactMotionTailwindadvanced

Overview

Event Calendar shows events in a month grid or a week of hours. Month cells hold chips — a dot and time for timed events, a tinted bar that continues across days for all-day and multi-day ones — and fold the rest into a "+2 more" button that opens the day in a native popover. The week view positions timed events by start and end on an hour grid, lays overlapping ones side by side, keeps all-day events in a row on top and draws a now line on today. Previous, Today and Next slide the period in the direction you travel (or switch instantly with reduced motion). Both views are ARIA grids with a single tab stop: arrows move by day and week, Page Up / Page Down by period, and Enter lists a day's events or creates one. Clicking an empty day calls onCreate(date), clicking an event calls onSelect(event), colours come per calendar, and every name and time is formatted by Intl for the locale. Today is SSR-safe: pass it, or it appears after hydration.

01Installation

Method 01 · Registry

CLI verified

Uses the shadcn CLI in a project initialised with shadcn init. Installs the file to components/ui/event-calendar.tsx and adds its dependencies.

npx shadcn@latest add https://wancraft.my/r/event-calendar.json

Method 02 · Copy and paste

  1. 1. Install the dependencies below.
  2. 2. Copy the source from the Code tab into components/ui/event-calendar.tsx.
  3. 3. Import it and adjust props.
npm install motion
Dependencies
motion
Peer
react ≥ 18, tailwindcss ≥ 4
Added
13 Oct 2026
Updated
—

02Usage

The default configuration. Change properties above to generate a tailored example in the Usage tab.

example.tsx
import { EventCalendar, type CalendarEvent } from "@/components/ui/event-calendar";

export function EventCalendarExample() {
  const events: CalendarEvent[] = [
    { id: "review", title: "Design review", start: "2026-10-13T10:00", end: "2026-10-13T11:30" },
    { id: "release", title: "Release", start: "2026-10-15", color: "#3FB984" },
    { id: "trip", title: "Penang trip", start: "2026-10-16", end: "2026-10-18" },
  ];

  return (
    <div className="w-full max-w-4xl">
      <EventCalendar
        events={events}
        today="2026-10-13"
        onCreate={(date) => console.log("create", date)}
        onSelect={(event) => console.log("open", event.id)}
      />
    </div>
  );
}

03Props

PropTypeDefaultDescription
eventsCalendarEvent[]—Required. { id, title, start, end?, allDay?, color?, calendar? } — "YYYY-MM-DD" for all-day (end inclusive) or "YYYY-MM-DDTHH:mm" wall time (end exclusive).
calendarsCalendarSource[][]{ id, label, color } — event colours by calendar id, and the legend.
view / defaultView / onViewChange"month" | "week"— / "month"Controlled or uncontrolled view.
date / defaultDate / onDateChangestring—Anchor day "YYYY-MM-DD" inside the visible period; defaults to today, else the first event.
todaystring—"YYYY-MM-DD". When omitted it is read in the browser after hydration, so server output stays deterministic.
localestring"en-GB"Month, weekday, date and time formatting via Intl.DateTimeFormat.
weekStartsOn"monday" | "sunday""monday"First column of every week.
maxChipsnumber3Chips per month cell; beyond it the last slot becomes "+N more".
hourHeightnumber44Pixels per hour in the week view.
scrollToHournumber8Hour the week view scrolls to when shown.
showLegendbooleantrueLegend of the calendars that have events.
onCreate(date: string) => void—Empty day click or Enter ("YYYY-MM-DD"), or a click on the week time grid ("YYYY-MM-DDTHH:mm", snapped to 30 min).
onSelect(event: CalendarEvent) => void—Event chip, block or popover item activated.
accentstring"#FF7448"Today marker, now line, focus rings and default event colour.
labelstring"Calendar"Accessible name prefix of the grid (followed by the period).

04Features

  • 01Month grid with event chips, multi-day bars that continue across cells and a "+N more" day popover
  • 02Week view with time-positioned blocks, side-by-side overlaps, an all-day row and a now line
  • 03Prev / Today / Next with a direction-aware slide (instant under reduced motion)
  • 04Empty day → onCreate(date); event → onSelect(event); per-calendar colours with a legend
  • 05Locale-aware month, weekday, date and time labels via Intl; Monday or Sunday weeks
  • 06SSR-safe today marker: pass today, or it is read after hydration
motion · springinput · clickinput · keyboardinput · focus

05Accessibility

  • Both views are role="grid" with column headers and one tab stop (roving tabindex): arrows move a day / week, Home / End go to the week's ends, Page Up / Page Down change month (or week); crossing the edge navigates the period.
  • Each day is a gridcell named with its full date, "today" and its event count (e.g. "Tuesday 13 October 2026, today, 5 events"), with aria-current="date" on today.
  • Enter or Space opens the day's events in a native popover dialog (focus moves to the first event; Escape closes only the popover and returns focus to the day) or, on an empty day, calls onCreate.
  • Event chips and blocks carry full names ("Design review, 10:00 to 11:30, Work") for pointer and screen-reader users; keyboard users reach them through the day popover.
  • The period title is a polite live region, view buttons use aria-pressed, and colours are never the only cue — titles and times are always written out.

06Limitations

  • Times are wall-clock strings without time-zone conversion; recurring events are not expanded.
  • Timed events spanning midnight appear in the all-day row of the week view; month cells do not reserve lanes for multi-day bars.