Event
A full calendar: Month, Week, Day and Agenda views with drag to move, resize, overlap layout and an editable event model — frappe-ui's Calendar, ported to React on espresso primitives.
Playground
The showcase page runs the full pattern: an app header, a view switcher, a side panel that opens on a click or a drag, drag-to-create on the grids, and a card-in-hand while an event is moved.
Installation
npx shadcn@latest add https://espresso-base-ui.vercel.app/r/event.jsonUsage
Hand the calendar a list of events. Each needs an id, a title, and its days as fromDate/toDate (YYYY-MM-DD) with fromTime/toTime (HH:MM) for timed events, or isFullDay for all-day ones. A multi-day event draws as one bar; a timed event shorter than a day that crosses midnight is split at it.
import { Event } from "@/components/ui/event"
const events = [
{
id: "1",
title: "Design review",
fromDate: "2026-10-14",
toDate: "2026-10-14",
fromTime: "10:00",
toTime: "11:00",
color: "green",
},
{
id: "2",
title: "Team offsite",
fromDate: "2026-10-15",
toDate: "2026-10-17",
isFullDay: true,
color: "blue",
},
]
export function TeamCalendar() {
return (
<Event
events={events}
config={{ defaultMode: "Week", isEditMode: true }}
onCreate={(event) => console.log("created", event)}
onUpdate={(event) => console.log("updated", event)}
onDelete={(id) => console.log("deleted", id)}
/>
)
}Views
Month is a strip of week rows — three rows per day and an “N more” line past them. Week and Day are time grids with an all-day row; overlapping events are laid out in columns, each pill carrying a cut ring where it lies over another. Agenda lists three months as cards of weeks, days with nothing on them left out. Keyboard shortcuts: m/w/d/a switch views, t jumps to today, arrow keys page.
Custom header
The header render prop replaces the built-in month label, movers and view switcher, handing you everything they were wired to.
<Event
events={events}
header={({ currentMonthYear, decrement, increment, setCalendarDate }) => (
<div className="flex items-center justify-between pb-3">
<span className="text-lg font-medium">{currentMonthYear}</span>
<div className="flex gap-1">
<Button variant="ghost" onClick={decrement}>Prev</Button>
<Button variant="ghost" onClick={() => setCalendarDate()}>Today</Button>
<Button variant="ghost" onClick={increment}>Next</Button>
</div>
</div>
)}
/>Your own panel
By default a click opens a read popover and, in edit mode, a double click opens an edit modal and a cell click a new-event modal. Supply onEventClick, onEventDblClick and onCellClick to replace those with your own surface — the showcase routes all three into a side panel.
// Replace the built-in popover and modal with your own panel:
<Event
events={events}
onEventClick={({ calendarEvent }) => openPanel(calendarEvent)}
onCellClick={({ date, time }) => openNewEventPanel(date, time)}
onUpdate={(event) => keepChange(event)}
/>API
Event
| Prop | Type | Default | Description |
|---|---|---|---|
| events | CalendarEvent[] | [] | The events to render. |
| config | Partial<EventConfig> | – | Overrides: defaultMode, isEditMode, hourHeight, scrollToHour, timeFormat ('12h' | '24h'), disableModes, enableShortcuts, eventIcons, showIcon, noBorder. |
| loading | boolean | – | Whether the visible range's events are still on their way — read by the Agenda's empty state. |
| onEventClick | ({ e, calendarEvent }) => void | – | Replaces the default popover on a single click. |
| onEventDblClick | ({ e, calendarEvent }) => void | – | Replaces the default edit modal on a double click. |
| onCellClick | (data: EventCellClickData) => void | – | Replaces the default new-event modal on a cell click. |
| onCreate / onUpdate / onDelete | (event) => void | – | Fired when an event is created, moved, resized, edited or deleted. |
| onRangeChange | ({ view, startDate, endDate }) => void | – | Fired when the visible range changes — the fetch window. |
| header | (props: EventHeaderSlotProps) => ReactNode | – | Replaces the built-in header. |
| eventPopoverContent | (props) => ReactNode | – | Replaces the popover's card for every event. |
| eventDescription / eventSuffix / eventParticipant | (props: EventRowSlotProps) => ReactNode | – | Fill the Agenda row's three open slots. |
CalendarEvent
| Prop | Type | Default | Description |
|---|---|---|---|
| id | string | number | – | Required for editing, dragging and deleting. |
| title | string | – | What the pill and the row say. |
| fromDate / toDate | string | – | First and last day, inclusive, as YYYY-MM-DD. |
| fromTime / toTime | string | – | HH:MM. A toTime of 00:00 ends as toDate begins. |
| isFullDay | boolean | – | Ignores the times and covers the days whole. |
| color | string | "green" | A palette name (gray, blue, green, amber, red, violet, pink, cyan, orange) or a hex the calendar derives a wash from. |
| type | string | – | Keys into config.eventIcons — a task mark beside the title. |
| isDraft / isDeclined | boolean | – | A draft draws as a plain tinted block; a declined event is struck through and claims no room in the grid. |
| participant / venue / description | string | – | What the popover and the Agenda's rows read. |