# Event calendar

A React component in [Beamline](https://beamline.io/)'s [Data & tables](https://beamline.io/components/data) category. Live demo: https://beamline.io/components/event-calendar

A schedule of events by day, week or month: events that overlap sit side by side, drag one to another time or day or pull its bottom edge to change its length (or grab it with Space and use the arrows), drag across empty time to make a new one; a line marks now, and a narrow room shows three days or a month of dots with the chosen day's list.

## Use it for

- Events with times people plan around: meetings, bookings, shifts, launches, on-call.
- People move or stretch events by hand and see clashes as overlaps.

## Not for

- Picking a date or a range in a form ([date-picker](https://beamline.io/components/date-picker), [date-range-picker](https://beamline.io/components/date-range-picker), calendar).
- Projects as bars over weeks with dependencies ([gantt-chart](https://beamline.io/components/gantt-chart)).
- Daily counts as colour ([activity-heatmap](https://beamline.io/components/activity-heatmap)).

## Anatomy

- toolbar: the range as a heading, previous / Today / next, the view switch (Month · Week · Day)
- week and day: day headers (today marked), an all-day row with lanes, hour gutter, day columns, events placed by time, the now line
- month: weekday header, a 6-week grid, multi-day bars in lanes, timed events as chips with their start, “+N more” opening the day's list
- event: colour bar, title, time; tentative is dashed, cancelled is struck through
- selection block while dragging across empty time; a status line when a change is refused
- live region announcing grab, every step and the drop

## Variants

- **0**: view: month · week · day (a narrow week shows three days)
- **1**: calendars: named sources with colours, so an event's colour is never its only label
- **2**: hourHeight, step (snap minutes), weekStartsOn, scrollToHour

## States

- loading (skeleton events in the grid)
- empty range (the grid with a quiet note)
- dragging (the event follows the pointer, snapped; its new time shows on it)
- resizing
- grabbed by keyboard (ring and raised paint)
- selecting a new slot
- refused (a status line says why; the event goes back)
- tentative, cancelled, read-only
- today, weekend and days outside the month

## Keyboard

- Tab reaches the events as one stop and the month's days as one stop
- ↑ ↓ move between events in time order; Home / End first / last; Enter opens one
- Space grabs the focused event: ↑ ↓ move it by the step (a week in month view), ← → by a day, Shift+↑ ↓ change its end; Space or Enter drops, Escape puts it back
- Month days: arrows move by day and week, Enter opens the day
- A keyboard move beyond the visible range pages to the event; Escape returns to its original range and focus, and drop keeps the committed event reachable.

## Motion

An event moved by keyboard or by someone else glides to its new place and size (top, height, left, width; one standard duration); while held by the pointer it follows at once, raised, then settles. Paging a range slides the grid in from the side it came from, 12 px, while it fades in. The now line moves once a minute. Reduced motion places everything at once; nothing runs at rest except the minute tick while today is in view.

## Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `value / defaultValue / onValueChange` | `CalendarEvent[] / (events, change) => void` | — | The events; change is { event, start, end, kind: move · resize }. |
| `calendars` | `{ id, label, color? }[]` | — | Named sources: an event's `calendar` picks its colour and its name. |
| `view / defaultView / onViewChange` | `"month" \| "week" \| "day"` | — | Default week. |
| `date / defaultDate / onDateChange` | `Date` | — | Any day inside the range shown. Default today (read in the browser). |
| `now` | `Date` | — | The present for the now line and Today; the browser's clock by default. |
| `canChange` | `(event, next: { start, end }) => true \| string` | — | Return a reason to refuse a move or resize ("Outside working hours"). |
| `onEventOpen` | `(event) => void` | — | Click or Enter on an event. |
| `onSlotSelect` | `({ start, end, allDay }) => void` | — | Dragging across empty time (or clicking it) proposes a new event. |
| `step` | `number` | — | Minutes every move snaps to. Default 15. |
| `hourHeight` | `number` | — | Pixels per hour in week and day views. Default 48. |
| `scrollToHour` | `number` | — | The hour the time grid opens at. Default 8. |
| `weekStartsOn` | `0 \| 1` | — | Sunday or Monday (default). |
| `locale` | `string` | — | Formats day and time names. |
| `loading` | `boolean` | — | Skeleton events; the grid keeps its place. |
| `emptyLabel` | `string` | — | What an empty range says. Default "Nothing scheduled". |
| `aria-label` (required) | `string` | — | The calendar's name: "Team calendar". |
| `className / classNames / style / ref` | `string / EventCalendarClassNames / CSSProperties / Ref<HTMLDivElement>` | — | The root and its documented parts (toolbar, grid, event, chip, day). |

## Dependencies

`lucide-react`

## Import

```tsx
import { EventCalendar } from "@/components/event-calendar/event-calendar";
```

## Get it

Event calendar is part of Beamline: 200+ components and 50+ complete screens for React that your coding agent (Claude Code, Codex, Cursor or any MCP client) installs into your app through Beamline's MCP server, as a ready-built package or as plain React source you can change. $49 launch price, one payment, no subscription, lifetime updates, one licence for your whole team. [Get Beamline](https://beamline.io/checkout?pack=complete) · [Connect your agent](https://beamline.io/connect)

## More in Data & tables

- [Kanban board](https://beamline.io/components/kanban-board): Work as cards in columns by stage: drag a card (or lift it with Space and walk it with the arrows) to another column or place, the cards around it make…
- [Data grid](https://beamline.io/components/data-grid): Virtualised grid for thousands of rows: multi-sort, search and value filters, resizable and pinnable columns, range selection, inline edit, footer…
- [Pivot table](https://beamline.io/components/pivot-table): Records summed up by the fields people choose: rows grouped by one or more fields that fold open level by level, columns split by another, one or more…
- [Query builder](https://beamline.io/components/query-builder): Conditions people build by hand to pick out records: each rule is a field, an operator that fits the field (contains, between, in the last N days, is any…
- [Log viewer](https://beamline.io/components/log-viewer): Log lines as they stream: it follows the newest line until you scroll up, then counts what arrived and brings you back in one click; filter by level with…
- [JSON viewer](https://beamline.io/components/json-viewer): A JSON value as a tree you can read and walk: branches open and close (one level, or all at once), closed ones preview what they hold, every value reads…
- [JSON diff viewer](https://beamline.io/components/json-diff-viewer): Two versions of a JSON value compared as one tree: added keys marked +, removed ones struck through with −, changed values read old → new, unchanged keys…
- [Import mapping](https://beamline.io/components/import-mapping): Map the columns of a file to the fields of one record type, see what every value becomes, then run the import and get an outcome for each row…
- [Diff table](https://beamline.io/components/diff-table): Proposed edits to a table before they happen: new rows green, removals struck through, changed cells old → new; each change can be left out and Apply…
- [Filter toolbar](https://beamline.io/components/filter-toolbar): The filters on a list kept in view: chips that remove themselves, Add filter with fields and their values, Clear all, and room for search or view controls.
- [Table](https://beamline.io/components/table): A plain, styled HTML table for small static data: header, body, footer, caption, numeric cells, three densities and a sticky header.
