Skip to content

Event Calendar

A month grid that shows events on the days they happen. People move between months, step through days with the arrow keys, pick an event, and add one from a day when the app allows it. The app owns the events and the month. Days with more events than fit show a +N more button that opens the full list for that day. UI permission is not security: the server must check again.

Blockv0.1.0experimentalWCAG 2.2 AAView spec

A ready-made piece, built from the library's own parts.

A block puts several components together into something you would otherwise assemble by hand. It never fetches data: you give it the data, or answer its callbacks. It is copied into your project like any component, with the parts it uses, so you can change anything.

Basic

Full screen (opens in a new tab)

October 2026

Sunday
Monday
Tuesday
Wednesday
Thursday
Friday
Saturday

Nothing picked yet

States

Full screen (opens in a new tab)

October 2026

Loading events

October 2026

No events this month

Events show here once they are scheduled.

October 2026

Couldn't load events

Permissions

Full screen (opens in a new tab)

Can create

October 2026

Sunday
Monday
Tuesday
Wednesday
Thursday
Friday
Saturday

Read only

October 2026

Sunday
Monday
Tuesday
Wednesday
Thursday
Friday
Saturday

Week starts monday

Full screen (opens in a new tab)

Oktober 2026

Montag
Dienstag
Mittwoch
Donnerstag
Freitag
Samstag
Sonntag

Installation

npx rdloom add event-calendar

Copies the source into src/components/rdloom. Edit it freely: rdloom upgrade merges later versions into your changes. It needs @internationalized/date, react-aria-components; add --install to install them.

Use another registry client
npx shadcn@latest add https://rdloom.vimalbhatt.com/r/event-calendar.json

Works, but without upgrade tracking.

Usage

import { EventCalendar, type EventCalendarEvent } from "@rdloom/react";

<div className="flex w-full justify-center">
  <div className="flex w-full max-w-4xl flex-col gap-3">
    <EventCalendar
      events={events}
      defaultMonth="2026-10"
      today="2026-10-08"
      onEventSelect={(event) => setPicked(`Event: ${event.title}`)}
      onDateSelect={(date) => setPicked(`Day: ${date}`)}
    />
    <p role="status" className="text-sm text-[var(--rd-color-text-muted)]">
      {picked}
    </p>
  </div>
</div>

API Reference

Defined by the spec. Components also accept the props of the React Aria component they wrap.

PropTypeDefault
eventsrequired

The events. start and end are ISO strings (2026-10-12 or 2026-10-12T09:30); the date part places the event, the time part is shown. An event with an end on a later day appears on each day it covers.

Array<{ id: string; title: string; start: string; end?: string; tone?: "neutral" | "info" | "success" | "warning" | "danger"; allDay?: boolean }>none
month

The month shown, as YYYY-MM. Use it with onMonthChange to control the month.

stringnone
defaultMonth

The month shown first when the month is not controlled, as YYYY-MM. Default: the current month.

stringnone
onMonthChange

Called with YYYY-MM when someone moves to another month, so you can load that month's events.

(month: string) => voidnone
weekStartsOn

The first day of the week, 0 for Sunday to 6 for Saturday.

number0
locale

A BCP 47 locale for month, day and time names, e.g. "de-DE". Default: en-US on the server and during hydration, then the browser's.

stringnone
today

The date treated as today, as YYYY-MM-DD. Default: the device's date. Set it for stable screenshots and tests.

stringnone
maxEventsPerDay

How many events a day shows before the rest go behind "+N more".

number3
label

Names the grid for screen readers, together with the month.

string"Calendar"
onEventSelect

Called when someone picks an event.

(event: { id: string; title: string; start: string; end?: string; tone?: "neutral" | "info" | "success" | "warning" | "danger"; allDay?: boolean }) => voidnone
onDateSelect

Called with YYYY-MM-DD when someone picks a day (click, Enter or Space).

(date: string) => voidnone
onCreate

Called with YYYY-MM-DD when someone uses a day's add button. The button shows only when this is set and permissions.create allows it.

(date: string) => voidnone
state

loading shows a skeleton grid and marks the calendar busy, empty says there are no events, error says they could not load. ready or no state shows the month.

DataStatenone
onRetry

Called by the Try again button in the error state.

() => voidnone
permissions

What the app allows: create. Hidden leaves the add buttons out; disabled keeps them reachable (aria-disabled) with the reason read, and nothing runs. This only changes what people see: the server must check again.

Permissions<"create">none
classNames

Extra class names for single parts, so you can restyle one part without editing the file. Keys: root, header, title, nav, grid, weekday, day, dayNumber, event, more, addButton, popover.

Partial<Record<"root" | "header" | "title" | "nav" | "grid" | "weekday" | "day" | "dayNumber" | "event" | "more" | "addButton" | "popover", string>>none

Accessibility

Role grid, WCAG 2.2 AA. Tested with axe and keyboard tests; screen reader checks are in the audit checklist.

Keyboard

  • Tab: moves into the grid on one day, then to the controls inside that day
  • Arrow keys: move between days, across month edges
  • Home and End: first and last day of the week
  • Page Up and Page Down: previous and next month
  • Enter or Space: picks the day
  • Escape: closes the +N more list

Screen readers announce

  • "Thursday 8 October 2026, 2 events, grid cell"
  • "9:00 AM Design review, button"
  • "3 more events on 8 October, button"

What your code must do

  • A grid with column headers for the weekdays and one row per week
  • Only one day is in the tab order; the arrow keys move it (roving tabindex)
  • Each day names its full date and how many events it has
  • Events are buttons named by time and title
  • The month heading is announced when the month changes
  • A disabled add button stays focusable and its reason is read
  • Event tone is shown by a dot and the color, never the only cue to the title

Block contract

Data
A list of events with ISO start and optional end, and a month.
Data states
loading, empty, error, ready
Permissions
create
Events
onMonthChange, onEventSelect, onDateSelect, onCreate, onRetry
You can replace
classNames for each part; weekStartsOn and locale; maxEventsPerDay; month or defaultMonth

Guidelines

Use it when

  • Showing what is scheduled across a month: bookings, deliveries, releases
  • Letting people open an event or start a new one from a day

Avoid it when

  • Picking one date for a form: use DatePicker or Calendar
  • Choosing a time on a day: use TimeSlotPicker
  • Hour by hour scheduling with overlapping events: this shows a month, not a day timeline

Don't

  • Treating the permissions prop as protection: UI permission is not security, so the server must check again
  • Loading every event the app has: load the visible month and use onMonthChange
  • Telling event types apart by color alone: put the type in the title

Design tokens

The semantic tokens this component uses. Change them once and every component follows; see Design tokens.

  • --rd-color-surface-default
  • --rd-color-surface-subtle
  • --rd-color-surface-selected
  • --rd-color-border-default
  • --rd-color-text-default
  • --rd-color-text-muted
  • --rd-color-action-primary
  • --rd-color-action-on-primary
  • --rd-color-focus-ring
  • --rd-color-feedback-info
  • --rd-color-feedback-info-subtle
  • --rd-color-feedback-success
  • --rd-color-feedback-success-subtle
  • --rd-color-feedback-warning
  • --rd-color-feedback-warning-subtle
  • --rd-color-feedback-danger
  • --rd-color-feedback-danger-subtle