Skip to content
Sign in
Generative UIEvent cardMenu

Event card

A calendar event from a tool result: when, in your zone and theirs, where, who is coming, a reply that rolls back if it fails, an amber note when it overlaps something else, and Add to calendar for Google, Outlook or an .ics file.

Times in two zones, a join link, guests with their replies, Accept, Maybe and Decline, and Add to calendar.

Installation

$ pnpm dlx shadcn@latest add https://chatcn.dev/r/event-card.json

Usage

tsx
import { EventCard } from "@/components/ui/chatcn/event-card"

const part = message.parts.find((p) => p.type === "tool-create_event")

<EventCard
  {...part?.output}
  loading={part?.state !== "output-available"}
  timeZone={Intl.DateTimeFormat().resolvedOptions().timeZone}
  onRsvpChange={(rsvp) => calendar.reply(part.output.id, rsvp)}
/>

Every widget takes plain props from a tool result, so map the tool output straight onto it. While the call runs, pass loading for a skeleton that matches the final layout; when it fails, pass error (and onRetry). Install with event-card and nothing else from the kit is required at runtime.

Times are formatted with Intl in timeZone (the viewer's by default). Pass secondaryTimeZone to show the same slot in the organizer's zone on a second line. When onRsvpChange returns a promise, the pressed reply shows a spinner, and a rejection puts the previous reply back with an error that says what to do.

Add to calendar

The menu opens Google Calendar or Outlook on the web with every field filled in, or downloads an .ics file built in the browser. The helpers live in chatcn-event-card and also run on the server, for example to attach an .ics to an email. Return false from onAddToCalendar to handle a provider yourself.

ts
import { buildIcs, googleCalendarUrl, outlookCalendarUrl } from "@/lib/chatcn-event-card"

const event = { title: "Checkout redesign review", start: "2026-10-29T14:00:00Z", end: "2026-10-29T14:45:00Z" }
googleCalendarUrl(event) // https://calendar.google.com/calendar/render?action=TEMPLATE&...
buildIcs(event) // BEGIN:VCALENDAR ... with escaping, 75 octet folding and a stable UID

Examples

Overlap warning

Pass conflicts and the card names the first clash and counts the rest, in amber with a warning glyph, before you reply.

Two meetings already sit in this slot, so the card says which ones before you reply.

Compact rows

One line per event for an agenda: date leaf, time, place, your reply and a join link.

States

Loading, a calendar error, no slot found, a cancelled event (replies hidden, details dimmed) with long text, and RTL with translated labels.

Loading skeletons, a calendar error, no slot found, and a cancelled event with long text.

Props

PropTypeNotes
titlestringWithout it the card shows the empty state
start, endstring | number | DateISO with offset, epoch ms or Date
allDaybooleanMulti-day ranges read All day, to Fri 13 Nov
timeZone, secondaryTimeZonestringIANA zones. Default is the viewer's
secondaryTimeZoneLabelstringName for the second zone: Lisbon
locale, hour12string, booleanFormatting. Default en-US and the locale's clock
location, locationHrefstringThe link opens maps
videoUrl, videoLabelstringJoin row with a Join link
description, calendarstringClamped to three lines
attendeesEventAttendee[]name, email?, avatar?, status?, organizer?. The count opens the full list
maxAvatarsnumberDefault 4, then a +N chip
status"confirmed" | "tentative" | "cancelled"
rsvp, defaultRsvp, onRsvpChange"accepted" | "tentative" | "declined"Controlled or uncontrolled. Return a promise for progress and rollback
conflicts{ title, time? }[]Amber overlap note
addToCalendar, onAddToCalendarboolean, (provider, event) => void | false
onJoin() => voidCalled when Join is clicked
layout"full" | "compact"Default full
labelsPartial<EventCardLabels>Every string the card writes, for translation
loading, error, onRetry, emptyStates

Keyboard

KeyAction
TabJoin, guests, the reply group, Add to calendar
ArrowLeft, ArrowRightMove between Accept, Maybe and Decline
Enter, SpaceReply, open the guest list or the calendar menu
EscapeClose the guest list or the menu

Accessibility

  • The date leaf is decorative; the date and time are read once, in full, from the time line.
  • Replies are a toggle group named Your reply. Sending and the result are announced through a polite status region.
  • Each guest's reply is written in words next to its icon, so green, amber and red are never the only signal.

Motion

  • The guest list and calendar menu grow from their trigger in 180 ms and close faster. Opened from the keyboard, they appear at once.
  • A pressed reply changes color in 150 ms. Nothing moves.