Flight Ticket

A booking or boarding pass the agent found, with local times, connections, seats and handoff actions.

Experience · Shares patterns with Timeline, QR Code

≈ 1,999 tokens per use (tool 1,492 · guidance 321 · typical payload 186)How token costs are estimated

Example: Flight Ticket

When is my flight to New York?

Air France
Booking referenceK7Q2LM

Fri, Oct 9Check-in opens in 3 h

Flight AF 1755

Lisbon (LIS) to New York (JFK), direct, flight AF 1755.

Terminal 1
Flight time 7 h 45 min
Terminal 1
Seat
23A
Class
Economy

When to use

  • The user asks about a flight they booked and the agent found the booking or boarding pass in their email or airline account: when it leaves, their seat, the gate, the booking reference.
  • They want to see their boarding pass, check in, or act on the booking.

When not to use

  • The agent doesn't have the booking from a tool result. Never invent times, seats, gates or references.
  • The user asks whether a flight is on time or where it is now. Use Flight Status.
  • The user is still searching for flights to buy.

How the agent should use it

  • Pass every time with the airport's UTC offset and its IANA timezone, exactly as the source gives them. Never convert times to the user's timezone.
  • Use one segment per flight, in order. The component works out each layover and flags connections under an hour.
  • Use passType: "boarding-pass" only when the source has an issued pass. Never generate barcode contents.
  • Every action is a handoff. On check-in, add-to-wallet, choose-seat, manage or share, open the airline's or your own flow. Never check in or change seats without confirming.

Times and timezones

Every time shows in its airport's own timezone, on a 24-hour clock as boarding passes print it: a flight from Lisbon at 07:40 reads 07:40 for a viewer anywhere. Screen readers hear the date, the city and its offset: "Departs Friday, October 9, 07:40 Lisbon time (GMT+1)". An arrival on a later local date gets a "+1" marker.

Times may carry an offset (2026-10-09T07:40:00+01:00, exact) or not (2026-10-09T07:40, read as wall-clock time at that airport, DST included). Formatting uses Intl only, no date library; the helpers are in flight-ticket-utils.ts (segmentMinutes, layovers, dayOffset, formatClock, defaultActions, …).

Relative text ("Check-in opens in 3 h", "Boarding in 40 min", "Departs in 1 day 3 h") is computed after mount, so server and client render the same markup, and refreshes every minute. Pass now to measure from a fixed moment instead (tests, demos, a server-rendered snapshot).

Layout

The card reads like a pass: one glance gives the time, the seat and the gate. It is drawn on a solid muted blue, an approved exception to GenUI's hollow cards for ticket documents; the action footer stays neutral. Pass the airline's light wordmark as airline.logoDark (and logo for reference) and it replaces the name in the header; without it the name shows as text.

  • Header: the airline and the booking reference (with a copy button), then one quiet line with the departure date and the state that matters next. While check-in is still closed it is a live countdown (13d 01:19:45, digits rolling, to the second); otherwise "Check-in is open", "Checked in" or, on a boarding pass, "Boarding in 40 min". On a single flight the flight number sits at the right of that line.
  • Route: each airport reads city, big airport code, then its local time and terminal. Between the codes a dashed line with a plane and the flight time. With connections the line becomes the journey bar: one pill per flight and a hatched segment per layover with the connection airport's code, widths following the minutes. The bar is decorative; the itinerary carries the same facts as text.
  • Pass fields: gate, seat and class, plus boarding time and group on a boarding pass, set large.
  • Itinerary for connections: a branch of dots joined by a line, one dot per flight ("AF 1325 06:00 LIS – 08:40 LHR") and a small one per layover, with a "Short connection" badge under an hour or "Change airports".
  • Passengers with their seats when there's more than one.

Boarding passes

A boarding pass gets a ticket stub: a perforated divider (a dashed border with two half-circle notches drawn in CSS), the passenger, and a 96px neutral code placeholder with "Scan in airline app" beside it.

The component never draws a scannable code. Barcode contents are security-sensitive and must come from the airline. To show a real pass, render your own wallet pass or the airline's code next to it, or hand off with add-to-wallet.

Actions

Buttons come from a closed set: check-in, add-to-wallet, choose-seat, manage and share. When the agent passes none, the defaults follow the state:

StateDefault actions
Boarding passAdd to wallet, Share
Booking, check-in openCheck in, Choose seat, Manage booking
Booking, checked inAdd to wallet, Manage booking
Booking, otherwiseManage booking, Choose seat

The first two show as buttons; any others go into a "More actions" menu. Check in is hidden once the passenger is checked in. Each action returns { action } once, locks its button, and the footer confirms it quietly ("Opening check-in…"); the other actions stay available. Otherwise the footer shows the source.

Installation

npx shadcn@latest add https://your-site.com/r/flight-ticket.json

Usage

app/api/chat/route.ts
import { tool } from "ai"
import { flightTicketTool } from "@/components/genui/flight-ticket.schema"

const tools = {
  // No execute: the user's first action is the tool result.
  [flightTicketTool.name]: tool({
    description: flightTicketTool.description,
    inputSchema: flightTicketTool.inputSchema,
  }),
}

Render the tool call and hand each action to your own flow:

components/message.tsx
import { FlightTicket } from "@/components/genui/flight-ticket"

function ToolPart({ part, addToolResult }) {
  if (part.type !== "tool-show_flight_ticket") return null

  return (
    <FlightTicket
      {...part.input}
      status={part.state === "input-streaming" ? "streaming" : undefined}
      result={part.state === "output-available" ? part.output : undefined}
      onResult={(result) => {
        if (result.action === "add-to-wallet") openWalletPass(part.input.bookingRef)
        if (result.action === "check-in") openCheckIn(part.input.bookingRef)
        if (part.state !== "output-available") {
          addToolResult({ tool: "show_flight_ticket", toolCallId: part.toolCallId, output: result })
        } else {
          sendMessage({ text: `Flight ticket action: ${JSON.stringify(result)}` })
        }
      }}
    />
  )
}

The agent receives one of:

{ action: "check-in" }
{ action: "add-to-wallet" }
{ action: "choose-seat" }
{ action: "manage" }
{ action: "share" }

With LangGraph

Push it from a graph node with ui.push({ name: "show_flight_ticket", 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 of results when several actions were taken. Those actions show as done and stay locked; they never call onResult again.

<FlightTicket
  {...part.input}
  result={part.state === "output-available" ? part.output : undefined}
/>

Schema

PropTypeNotes
bookingRefstringThe PNR, 5–8 letters or digits, e.g. "K7Q2LM". Copy button included. Data-bound
airline{ name, code }Data-bound
passengers{ name, seat?, class? }[]1–9. class: economy, premium, business, first. Data-bound
segmentsSegment[]1–4 flights in order. Data-bound
segments[].from / to{ code, city, terminal?, gate?, timeZone }IATA code and IANA timezone; gate on departure only
segments[].departAt / arriveAtstringISO 8601, with the airport's offset or as local time
segments[].durationnumber?Minutes; computed when absent
segments[].boardingAt, boardingGroup, operatedBystring?
passType"booking" | "boarding-pass"
checkInOpensAtstring?ISO 8601. Data-bound
checkedInboolean?Data-bound
barcode{ format: "qr" | "pdf417" | "aztec", label? }?Placeholder only; never the payload
source{ label, url? }url http(s) only. Data-bound
actionsFlightTicketAction[]?Up to 5; the first is primary. Defaults by state

Host props: status (streaming, ready, submitting, done), now, onResult, result and className.

Source

On this page