Restaurant Reservation
Book a table from a restaurant's real availability, or show an existing reservation.
Experience · Shares patterns with Slot Picker, Generated Form
≈ 2,357 tokens per use (tool 1,708 · guidance 331 · typical payload 318)How token costs are estimated
Example: Restaurant Reservation
Book a table for 4 at Cal Pep on Friday
Cal Pep
Catalan seafood tapasEl Born
When to use
- The user wants a table at a specific restaurant: show its real open times for their party.
- The user asks what's free at a time or on a day at one restaurant.
- The user asks about an existing reservation (
mode: "confirmation").
When not to use
- The user is still choosing a restaurant. Show the options as Entity Cards first.
- The agent hasn't fetched availability for the party size. Never invent times.
- It's a generic appointment, not a restaurant. Use the Slot Picker.
How the agent should use it
- Pass days, slots, areas and notes exactly as the source returned them, for the size in
party.size. Include fully booked days with emptyslots; they show faded, with a "Fully booked" tooltip. - Set
defaultDateto the day the user asked for. - Only list occasions and dietary options the booking system accepts. Omit
requestsotherwise. - On
reserve, start the host's booking flow with exactly the returned date, time, size and area. Never treat it as booked until the source confirms. The provider collects any deposit. - On
change-party, fetch availability for the new size and show the component again. - On
cancel-reservation, cancel through the booking tool and confirm in one line. Ondismiss, do nothing. - Never ask for card details in chat.
Interaction
Select mode. The party size stepper and the seating filter share one row. The stepper starts at party.size; changing it never invents availability: the times are hidden and the card says "Availability is for 4 guests.", with a Check for 6 button that returns change-party. Days are a calendar strip (a radio group; arrow keys move between them): the month, then a serif date, the weekday and a tick per day, scrolling sideways with arrows when there are more. Days with no slots are disabled and faded, with a "Fully booked" tooltip. Times are grouped into Lunch (before 16:00) and Dinner. Times are compact buttons; each one's seating areas show in a tooltip while the filter is on Any and the restaurant has more than one area; slot notes from the source ("2-hour limit") are read to screen readers and shown once the time is picked. The picked time is the card's headline, with the day, party, seating and slot note under it, and the deposit for the whole party when there is one ("€80 deposit, refundable until 24 h before"). Choosing a time with several areas adds a small seating choice (with "No preference"). Occasion, dietary needs, the note, the policies (cancellation, deposit, dress code, table time) and the source sit behind one Requests and policies disclosure, collapsed by default. The restaurant's time zone is announced with the times for screen readers. Reserve stays disabled until a time is picked and never books or charges anything: it hands the choice to the host, and the provider collects any deposit.
Confirmation mode. The time is the headline, next to the booking state (a waitlist shows "On the waitlist"), with one line under it for the date, party, seating and how far away it is ("In 2 days", from the viewer's clock after mount or the now prop), the waitlist position when the notes give one, then the confirmation number with a copy button. One Reservation details disclosure holds the guest name, notes, policies, the address with Directions, Website and a tel: phone link, and the source. The footer has Modify, an Add to calendar button (hidden on the waitlist) and a quiet red Cancel reservation (or Leave waitlist) on the left, which asks for a second, explicit click before it returns cancel-reservation.
All times are wall-clock HH:mm in restaurant.timeZone and are formatted with Intl in the viewer's locale.
Installation
npx shadcn@latest add https://your-site.com/r/restaurant-reservation.json
Usage
import { tool } from "ai"
import { restaurantReservationTool } from "@/components/genui/restaurant-reservation.schema"
const tools = {
// No execute(): the call is completed by the user's choice in the UI.
[restaurantReservationTool.name]: tool({
description: restaurantReservationTool.description,
inputSchema: restaurantReservationTool.inputSchema,
}),
}import { RestaurantReservation } from "@/components/genui/restaurant-reservation"
function ToolPart({ part, addToolResult }) {
if (part.type !== "tool-reserve_table") return null
return (
<RestaurantReservation
{...part.input}
status={part.state === "input-streaming" ? "streaming" : undefined}
result={part.state === "output-available" ? part.output : undefined}
onResult={(result) =>
addToolResult({
tool: "reserve_table",
toolCallId: part.toolCallId,
output: result,
})
}
/>
)
}The agent receives one of:
{ action: "reserve", date: "2026-10-16", time: "20:00", size: 4, area: "outdoor", occasion: "Birthday", dietary: ["Vegetarian"], note: "A high chair, please" }
{ action: "change-party", size: 6 }
{ action: "dismiss" }
{ action: "modify" } // confirmation mode
{ action: "add-to-calendar" } // confirmation mode
{ action: "cancel-reservation" } // confirmation mode, after the second clickarea, occasion, dietary and note are left out when the user didn't choose them.
With LangGraph
Push it from a graph node with ui.push({ name: "reserve_table", props: { toolCallId, ...args } }, { message }), and render the thread's UI messages with the Renderer.
Restoring from history
Pass the stored result back as result (or an array of results, for several confirmation actions). Select mode shows what was sent ("Sent to booking", "Asked for tables for 6 guests", "Cancelled. Nothing was booked."); confirmation mode marks the used actions ("Cancellation requested"). It never calls onResult again.
<RestaurantReservation
{...part.input}
result={part.state === "output-available" ? part.output : undefined}
/>Schema
| Prop | Type | Notes |
|---|---|---|
mode | "select" | "confirmation" | |
restaurant | { name, cuisine?, priceLevel?, address, neighborhood?, timeZone, rating?, phone?, url?, logo? } | Data-bound. priceLevel 1–4; rating is { score, outOf, count? }; count is read to screen readers only. logo is { url, darkUrl?, alt? }, the restaurant's logo (http(s) or a same-origin path, never a photo), shown in a white 48px tile beside the name in place of the fork-and-knife icon; it falls back to the icon if it fails to load. Never invent a URL. |
party | { size, min?, max? } | Data-bound. The size the availability was fetched for. Stepper limits default to 1–20 |
days | { date, slots }[] | Data-bound. Up to 7 days. Empty slots shows the day as full |
defaultDate | string? | YYYY-MM-DD, the day shown first |
policies | { cancellation?, deposit?, dressCode?, maxDurationMinutes? } | Data-bound. deposit is { amount, perPerson?, refundableUntil? } |
currency | string? | ISO 4217. Required with a deposit |
requests | { occasions?, dietary?, allowNote? } | Data-bound. What the booking system accepts. Note is up to 200 characters |
confirmation | { number, bookingState, date, time, size, area?, guestName, notes? } | Data-bound. Confirmation mode. bookingState: confirmed, pending, waitlist, cancelled |
source | { label, url? } | Data-bound. Where the availability or reservation came from |
Slot
| Field | Type | Notes |
|---|---|---|
time | string | Data-bound. HH:mm, restaurant local time |
areas | ("indoor" | "outdoor" | "bar" | "counter" | "private")[]? | Data-bound. Seating open at this time |
note | string? | Data-bound. e.g. "Bar seating only", "2-hour limit" |
Host props: status (streaming, ready, submitting, reserved, change-requested, dismissed, cancel-requested, done), onResult, result, now and className.
Source
Seat Map
Let the user pick seats on a real layout (a plane, a cinema, a theatre with curved rows and several levels, a train or bus coach, an event floor), one per passenger or a set count.
Option Builder
Pick a combination of options where each choice carries metrics, and see the totals, budgets and score update before continuing.
