Slot Picker
Let the user pick a date and time from real availability, shown in a stated timezone.
≈ 2,062 tokens per use (tool 575 · guidance 268 · typical payload 1,219)How token costs are estimated
Example: Slot Picker
Set up a follow-up call with Dana from Bluepeak. Any time in the coming months works.
Follow-up call with Dana Reyes
30 minNew York time (GMT-4)
When to use
- The user needs to pick a time for something with real availability: a call, a table, a service appointment.
- The agent fetched open slots from a calendar or booking system.
When not to use
- The agent hasn't checked availability. Fetch it first; never offer invented times.
- There's exactly one possible time. Ask for a yes or no in text.
- The user already named a time that's free. Book it, or confirm it with a Change Preview.
How the agent should use it
- Every slot comes from a tool result, with its real
id. - Always set
timezoneto the zone the times are in, and use the user's zone when possible. - Include taken slots as
available: falsewhen it helps the user see the shape of the day; drop days with nothing at all. - Book only the
slotIdreturned withselect, and re-check it's still free before booking.
Interaction
Days are toggle buttons (aria-pressed) that scroll sideways on narrow screens; a day with no open slots is disabled, its date struck through and announced as "full" (each day also announces how many times are open). The first day with an open slot is shown first. Times for the chosen day are toggle buttons in a labelled group; taken slots are disabled, struck through and announced as "taken". Confirm stays disabled until a time is picked; the footer then shows the full day and time. After confirming, the chosen time is the card's headline.
start can be wall-clock HH:mm in timezone, or a full ISO date-time with an offset, which is converted into timezone. Dates and times are formatted with Intl in the viewer's locale.
Installation
npx shadcn@latest add https://your-site.com/r/slot-picker.json
Usage
import { tool } from "ai"
import { slotPickerTool } from "@/components/genui/slot-picker.schema"
const tools = {
// No execute(): the call is completed by the user's choice in the UI.
[slotPickerTool.name]: tool({
description: slotPickerTool.description,
inputSchema: slotPickerTool.inputSchema,
}),
}import { SlotPicker } from "@/components/genui/slot-picker"
function ToolPart({ part, addToolResult }) {
if (part.type !== "tool-pick_slot") return null
return (
<SlotPicker
{...part.input}
status={part.state === "input-streaming" ? "streaming" : undefined}
result={part.state === "output-available" ? part.output : undefined}
onResult={(result) =>
addToolResult({
tool: "pick_slot",
toolCallId: part.toolCallId,
output: result,
})
}
/>
)
}The agent receives one of:
{ action: "select", slotId: "2026-10-14T12:00", date: "2026-10-14", start: "12:00", label: "Wednesday, October 14 at 12:00 PM" }
{ action: "cancel" }start is returned exactly as it was given.
With LangGraph
Push it from a graph node with ui.push({ name: "pick_slot", props: { toolCallId, ...args } }, { message }), and render the thread's UI messages with the Renderer.
Restoring from history
Pass the stored result back as result. The picker shows the chosen day and time (or the cancelled state) and never calls onResult again.
<SlotPicker
{...part.input}
result={part.state === "output-available" ? part.output : undefined}
/>Schema
| Prop | Type | Notes |
|---|---|---|
kind | "call" | "table" | "service" | "meeting" | "other"? | What's being booked. Shown as a coloured icon beside the title |
title | string | What's being booked: "Book a follow-up call" |
description | string? | Short; joins durationLabel and the timezone on one line under the title |
timezone | string | IANA zone, e.g. America/New_York. Shown as "New York time (GMT-4)" |
days | { date, slots }[] | Data-bound. 1–366 days with times, date as YYYY-MM-DD. The picker shows a 12-month calendar strip from the first day; days without times are faded, with a "No open times" tooltip. The arrows scroll it, and the month label follows the days in view |
durationLabel | string? | "30 min", "2 hours" |
submitLabel | string? | Verb for the confirm button. Default "Confirm" |
Slot
| Field | Type | Notes |
|---|---|---|
id | string | Data-bound. Returned in the result |
start | string | Data-bound. HH:mm, or ISO date-time with offset |
end | string? | Same format as start |
label | string? | Overrides the formatted time |
available | boolean | Data-bound. Default true; false shows the slot as taken |
Host props: status (streaming, ready, submitting, selected, cancelled), onResult, result and className.