Event Details
One event the user is interested in or invited to, with the time in the event's timezone, the venue, key facts, agenda and speakers, and RSVP and calendar actions.
Experience · Shares patterns with Summary Card, Map, Calendar
≈ 1,896 tokens per use (tool 1,320 · guidance 358 · typical payload 218)How token costs are estimated
Example: Event Details
Tell me about the design meetup on Thursday

Design Systems Night: tokens in practice
- Price
- Free
- Capacity
- 84 going · 120 spots
- Accessibility
- Step-free access · Accessible restroom
Two talks on design tokens, then lightning demos. Bring a laptop if you'd like to follow along.
Agenda
- 7:00 PMTokens that survive a rebrandPriya Raman
- 7:40 PMFrom design variables to codeTomás Ferreira
When to use
- The user asks about one specific event they're interested in or invited to: a conference, concert, meetup, webinar, wedding, match or class.
- They want the when and where, the dress code, the agenda or who's speaking, or to RSVP, add it to their calendar or get tickets.
When not to use
- The agent doesn't have the event from a tool result (an invite, a calendar, a ticketing or event page). Never invent times, venues, prices or attendee counts.
- The user wants their ticket or entry code. Use Event Pass.
- There are several events to choose from, or a whole day's schedule. Use a list or the Calendar.
How the agent should use it
- Pass
startandendwith the venue's UTC offset and the event's IANAtimeZone, exactly as the source gives them. Never convert times to the user's timezone. - Use a date-only
startfor all-day events ("2026-10-24");endis then the last day. - Only include
rsvpwhen the user can actually respond, with their current answer (noneif they haven't). - Keep the description to facts the other fields don't cover, up to 600 characters, with
**bold**for one or two key details. - Every action is a handoff. On
rsvp, record the answer through the event's own system;get-ticketsopensurl;add-to-calendarandsharehappen on the device. - Lifecycle: set
expiresAton the call to the event's end (or the end of its last day), so a past event turns inert in the Renderer instead of offering RSVP and tickets. When the agent shows the same event again with updated details (a new time, a changed venue), supersede the old call.fetchedAtisn't needed.
Times and timezones
The date line shows in the event's own timezone: "Thu, Oct 15 · 6:30–9:00 PM". The line below names the zone ("New York time"), adds the start in the viewer's own time when it differs ("3:30 PM your time"), and how far away it is ("in 7 days", "tomorrow", "happening now", "ended").
| Event | Date line |
|---|---|
| Same day | Thu, Oct 15 · 6:30–9:00 PM |
| No end | Thu, Oct 15 · 6:30 PM |
| Past midnight | Sat, Oct 24 · 4:00 PM – Sun 1:00 AM |
| Several days | Thu, Oct 15, 9:00 AM – Sat, Oct 17, 5:00 PM |
| All day | Sat, Oct 24 · All day |
| Several days, all day | Thu, Oct 15 – Sat, Oct 17 |
The year appears when it isn't the current one. The relative text and the viewer's time are computed after mount, so server and client render the same markup; pass now to measure from a fixed moment (tests, demos). Agenda times may be "18:30" (local time at the event) or ISO date-times. Formatting uses Intl only; the helpers are in event-details-utils.ts (resolveWhen, dateRangeText, relativeText, capacityText, directionsUrl, buildIcs, …).
Layout
- Header: the title.
kindpicks the people heading ("Speakers", "Lineup") and isn't shown on its own. - When and where: the date line as the largest text, one quiet line under it, the venue with its address and a Directions link (an OpenStreetMap search), and for online or hybrid events the platform with its join link.
- Key facts: price, capacity ("84 going · 120 spots"), dress code, age and accessibility, only those given.
- Description, then the agenda as a compact timeline and the speakers (or lineup).
- An optional image from the event's own data (http(s) or
data:imageonly) sits on top.
Actions
| Button | When | Result |
|---|---|---|
| Going / Maybe / Can't go | rsvp is set and its deadline hasn't passed | { action: "rsvp", state } |
| Get tickets (Register when free) | url is http(s) | { action: "get-tickets" }, and opens url |
| Add to calendar | Always | { action: "add-to-calendar" }, and downloads an .ics |
| Share (icon button) | Always | { action: "share" }, and opens the share sheet (or copies the details) |
After an answer, the RSVP locks to it ("You're going") with a Change button that reopens the choice; a new answer sends another rsvp result. Answers close after rsvp.deadline. The calendar file is built in the browser: timed events in UTC, date-only events as all-day, with text escaped and long lines folded. Add to calendar, Get tickets and Share report once each.
Installation
npx shadcn@latest add https://your-site.com/r/event-details.json
Usage
import { tool } from "ai"
import { eventDetailsTool } from "@/components/genui/event-details.schema"
const tools = {
// No execute: the user's first action is the tool result.
[eventDetailsTool.name]: tool({
description: eventDetailsTool.description,
inputSchema: eventDetailsTool.inputSchema,
}),
}Render the tool call and hand each action to your own flow:
import { EventDetails } from "@/components/genui/event-details"
function ToolPart({ part, addToolResult }) {
if (part.type !== "tool-show_event") return null
return (
<EventDetails
{...part.input}
status={part.state === "input-streaming" ? "streaming" : undefined}
result={part.state === "output-available" ? part.output : undefined}
onResult={(result) => {
if (result.action === "rsvp") saveRsvp(result.state)
if (part.state !== "output-available") {
addToolResult({ tool: "show_event", toolCallId: part.toolCallId, output: result })
} else {
sendMessage({ text: `Event action: ${JSON.stringify(result)}` })
}
}}
/>
)
}The agent receives one of:
{ action: "rsvp", state: "going" } // or "maybe", "not-going"
{ action: "add-to-calendar" }
{ action: "get-tickets" }
{ action: "share" }With LangGraph
Push it from a graph node with ui.push({ name: "show_event", props: { toolCallId, ...args } }, { message }), and render the thread's UI messages with the Renderer.
Restoring from history
When a conversation is reloaded, pass the stored result back as result, or an array when several actions were taken. The latest rsvp restores the locked answer (Change still works), and the other actions show as done. Restoring never calls onResult.
<EventDetails
{...part.input}
result={part.state === "output-available" ? part.output : undefined}
/>Schema
| Prop | Type | Notes |
|---|---|---|
title | string | Data-bound |
kind | "conference" | "concert" | "meetup" | "webinar" | "party" | "sports" | "class" | "other" | Picks the people heading; not shown on its own |
start / end | string | ISO 8601 with the venue's offset, or local time. A date only is all-day; end is then the last day. Data-bound |
timeZone | string | The event's IANA timezone. Data-bound |
allDay | boolean? | Treat the dates as all-day |
venue | { name, address?, online?: { platform, url? } }? | Directions with an address; join link http(s) only. Data-bound |
description | string? | Up to 600 characters, plain text with **bold** |
agenda | { time, title, speaker? }[]? | Up to 12. time is "18:30" or ISO. Data-bound |
people | { name, role? }[]? | Speakers or lineup, up to 12. Data-bound |
price | "free" | { amount, currency }? | ISO 4217 currency. Data-bound |
capacity | { going, max? }? | Data-bound |
dressCode, ageLimit | string? | |
accessibility | string[]? | Up to 8, e.g. "Step-free access", "Live captions". Data-bound |
rsvp | { state: "none" | "going" | "maybe" | "not-going", deadline? }? | Adds the RSVP buttons. Data-bound |
url | string? | Tickets or registration, http(s) only. Data-bound |
image | { src, alt }? | http(s) or data:image. Data-bound |
Host props: status (streaming, ready, submitting, responded), locale, now, onResult, result and className.