Venue Map

Pick seats at a stadium, arena or concert hall. Choose a section on an overview map shaded by price or seats left, then pick seats in it, or ask the agent for the best available.

Experience · Shares patterns with Seat Map, Region Map

≈ 6,567 tokens per use (tool 1,320 · guidance 325 · typical payload 4,922)How token costs are estimated

Example: Venue Map

Two tickets for Saturday's match behind the goal.

Northgate FC v Harbour City

Northgate Park · Sat, Oct 10, 3:00 PM

2

Sections in order: each tier clockwise from the top. Arrow keys move between sections; Enter opens one.

Section 116, lower tier, side view, from €85, 91 left, accessible seating
Section 101, lower tier, side view, from €85, 96 left, accessible seating
Section 102, lower tier, corner, €65, 133 left
Section 103, lower tier, behind the goal, from €55, 30 left
Section 104, lower tier, behind the goal, sold out
Section 105, lower tier, behind the goal, from €55, 104 left
Section 106, lower tier, corner, €65, 141 left
Section 107, lower tier, side view, from €85, 38 left
Section 108, lower tier, side view, from €85, 75 left, accessible seating
Section 109, lower tier, side view, from €85, 112 left, accessible seating
Section 110, lower tier, corner, €65, 9 left
Section 111, lower tier, behind the goal, from €55, 46 left
Section 112, lower tier, behind the goal, from €55, 97 left, accessible seating
Section 113, lower tier, behind the goal, from €55, 120 left
Section 114, lower tier, corner, €65, 17 left
Section 115, lower tier, side view, from €85, 54 left
Section 216, middle tier, side view, €70, 144 left
Section 201, middle tier, side view, €70, 9 left
Section 202, middle tier, corner, €55, 46 left
Section 203, middle tier, behind the goal, €45, 83 left
Section 204, middle tier, behind the goal, €45, 120 left
Section 205, middle tier, behind the goal, sold out
Section 206, middle tier, corner, €55, 54 left
Section 207, middle tier, side view, €70, 91 left
Section 208, middle tier, side view, €70, 128 left
Section 209, middle tier, side view, €70, 25 left
Section 210, middle tier, corner, €55, 62 left
Section 211, middle tier, behind the goal, €45, 99 left
Section 212, middle tier, behind the goal, €45, 136 left, accessible seating
Section 213, middle tier, behind the goal, €45, 33 left
Section 214, middle tier, corner, €55, 70 left
Section 215, middle tier, side view, €70, 107 left
Section 316, upper tier, side view, sold out
Section 301, upper tier, side view, €50, 62 left
Section 302, upper tier, corner, €40, 99 left
Section 303, upper tier, behind the goal, €32, 136 left
Section 304, upper tier, behind the goal, €32, 33 left
Section 305, upper tier, behind the goal, €32, 70 left
Section 306, upper tier, corner, €40, 107 left
Section 307, upper tier, side view, €50, 144 left
Section 308, upper tier, side view, €50, 41 left
Section 309, upper tier, side view, €50, 78 left
Section 310, upper tier, corner, €40, 115 left
Section 311, upper tier, behind the goal, €32, 12 left
Section 312, upper tier, behind the goal, €32, 49 left
Section 313, upper tier, behind the goal, €32, 86 left
Section 314, upper tier, corner, €40, 123 left
Section 315, upper tier, side view, €50, 20 left
Shade sections by
  • Price from, light to strong: €20–€40, €40–€60, €60–€80, €80–€100
  • Sold out
  • Accessible

When to use

  • The user is buying tickets for a stadium, arena or concert hall and where they sit matters: "behind the goal", "not too far back", "two together".
  • The agent fetched the venue's sections, prices and live availability from the ticketing system.

When not to use

  • General admission or a single price zone: there's nothing to choose. Ask how many tickets and book.
  • A small room with one seating block, such as a cinema or a theater's stalls. Use the Seat Map.
  • The agent doesn't have real availability. Fetch it first; never invent sections or prices.

How the agent should use it

  • Every section, price and seat count comes from the ticketing system, with the system's real ids.
  • Lay ring sections out with angle and span around the focal point: 0° is the top, angles go clockwise, and ring 1 is nearest. A section may cross the top (angle: 348.75, span: 22.5). Floor and standing sections use floor: { x, y, w, h } in 0–1 units of the floor area, front first.
  • Send rows only for sections you fetched seat by seat. When the user opens a section without rows, the result is open-section: fetch that section and call again with its rows and initialSection set to it.
  • Set count, together and maxPrice from what the user said. The user can change the count and the price limit on the card.
  • On best-available, pick the seats yourself within the count, price limit and accessibility the user set, and say which ones you chose.
  • This only picks seats. Reserve them after a select result and confirm the total before taking payment.
  • Lifecycle: availability is live, so set fetchedAt and staleAfterMs (about 2 minutes) on the call; the Renderer then shows "may have changed" with a refresh. Once seats are held for the user, set expiresAt to the end of the hold. When the agent sends fresh availability or a section's rows in a new call, mark the old call superseded (supersededBy).

Interaction

InputAction
Click a section, or Enter / Space in the section listOpens it: its seats, or a loading state while the agent fetches them
↑ ↓ ← → in the section listMove between sections; the focused one is outlined on the map with its details
Home End in the section listFirst or last section
Tickets stepper, Max price slider, Accessible onlyChange the count and dim sections that don't fit
Price / Seats left (in the legend row)Switch what the shading shows
Best available (footer, on the overview)Hands the choice to the agent
Arrow keys, Home End in the seat gridMove between available seats, as in the Seat Map
Back arrow beside the section nameReturns to the overview; picks in another section are cleared when you open it

The map itself is hidden from assistive tech. Keyboard and screen reader users get a listbox of every section in reading order (floor first, then each tier clockwise from the top), grouped by tier, with the same facts as the tooltip: "Section 112, lower tier, behind the goal, from €55, 97 left". Sold-out sections are muted and say "Sold out"; sections above the max price or without accessible seating are dimmed and say why, so nothing relies on color alone. The shading uses one hue from light to strong, explained in the legend by a single ramp with its two ends; screen readers hear every step's range.

In the seat step, the grid is the Seat Map's ARIA grid with a roving tabindex. Picking more than the count drops the oldest pick and each pick is announced. The heading above the grid names the section with its view and price per seat (the tier is read to screen readers). The footer shows the chosen seats large ("Row F · 7, 8") with the total beside them, and one quiet line for progress and whether the seats are together; when a seat's price is unknown, the total is left out and the line says "Total at checkout". The legend under the grid lists only taken, unavailable and wheelchair seats, and a hovered or focused seat's details take its place.

Installation

npx shadcn@latest add https://your-site.com/r/venue-map.json

Usage

app/api/chat/route.ts
import { tool } from "ai"
import { venueMapTool } from "@/components/genui/venue-map.schema"

const tools = {
  // No execute(): the call is completed by the user's choice in the UI.
  [venueMapTool.name]: tool({
    description: venueMapTool.description,
    inputSchema: venueMapTool.inputSchema,
  }),
}
components/message.tsx
import { VenueMap } from "@/components/genui/venue-map"

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

  return (
    <VenueMap
      {...part.input}
      status={part.state === "input-streaming" ? "streaming" : undefined}
      result={part.state === "output-available" ? part.output : undefined}
      onResult={(result) =>
        addToolResult({
          tool: "pick_venue_seats",
          toolCallId: part.toolCallId,
          output: result,
        })
      }
    />
  )
}

The agent receives one of:

{ action: "open-section", sectionId: "nwp-112" }
{ action: "best-available", count: 2, together: true, maxPrice: 60 }
{ action: "select", sectionId: "nwp-112", seats: [{ seatId: "112-F-7", price: 55 }, { seatId: "112-F-8", price: 55 }] }
{ action: "dismiss" }

open-section is a request, not a decision. With the AI SDK it completes the call, and the card shows "Asked for seats in section 112" until the agent's next call (with the section's rows and initialSection) replaces it. A host that keeps the same card open can instead update its props in place: the card shows "Loading seats for section 112…" and switches to the seat grid when the rows arrive. best-available includes maxPrice only when the user lowered the slider, and accessible: true only when "Accessible only" is on. In select, seats come in map order with price when it's known.

With LangGraph

Push it from a graph node with ui.push({ name: "pick_venue_seats", props: { toolCallId, ...args } }, { message }), and render the thread's UI messages with the Renderer.

Restoring from history

Pass the stored result back as result. A select shows the chosen seats in their section, read-only; best-available, open-section and dismiss show what the user asked for. It never calls onResult again.

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

Schema

PropTypeNotes
venue{ name }Data-bound
event{ title, start }?Data-bound. start is ISO 8601, shown in the venue's local time
focal{ kind, label? }pitch, court, rink (drawn in the middle) or stage (drawn at the top)
sectionsSection[]Data-bound. Every section on sale. Up to 120
currencystringISO 4217 code
countnumberTickets wanted, 1–10. Default 2
togetherbooleanThe user wants seats side by side. Default true
maxPricenumber?Price per ticket. Sections whose lowest price is above it are dimmed
colorBy"price" | "availability"What the shading shows first. Default "price"
initialSectionstring?Section id to open first, after an open-section request

Section is { id, label, tier?, position, price?, available, accessible?, view?, rows? }. tier is floor, lower, middle or upper; it groups the screen reader list and defaults from the ring (floor for floor rectangles). position is { angle, span, ring } (ring 1–3) or { floor: { x, y, w, h } }. price is { from, to? }: set only from when every seat costs the same. available is the number of seats left; 0 shows the section as sold out. view is a short phrase such as "Behind the goal".

rows are { label, seats }[], front row first. Each entry in seats is a seat { id, label?, state, price?, features? } or a stairway { gap: true }. state is available, taken or blocked; features are aisle, accessible, companion, restricted-view and extra-legroom. A seat without price uses the section's single price.

Host props: status (streaming, ready, submitting, selected, best-available, section-requested, dismissed), onResult, result and className.

Source

On this page