Event

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.json

Usage

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

PropTypeDefaultDescription
eventsCalendarEvent[][]The events to render.
configPartial<EventConfig>–Overrides: defaultMode, isEditMode, hourHeight, scrollToHour, timeFormat ('12h' | '24h'), disableModes, enableShortcuts, eventIcons, showIcon, noBorder.
loadingboolean–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

PropTypeDefaultDescription
idstring | number–Required for editing, dragging and deleting.
titlestring–What the pill and the row say.
fromDate / toDatestring–First and last day, inclusive, as YYYY-MM-DD.
fromTime / toTimestring–HH:MM. A toTime of 00:00 ends as toDate begins.
isFullDayboolean–Ignores the times and covers the days whole.
colorstring"green"A palette name (gray, blue, green, amber, red, violet, pink, cyan, orange) or a hex the calendar derives a wash from.
typestring–Keys into config.eventIcons — a task mark beside the title.
isDraft / isDeclinedboolean–A draft draws as a plain tinted block; a declined event is struck through and claims no room in the grid.
participant / venue / descriptionstring–What the popover and the Agenda's rows read.

Search documentation

Search the component docs