Map

Places on an interactive map, to show a route, pick a place, drop a pin, lay out a multi-day itinerary or find places to stay near a landmark or venue.

Pattern

≈ 3,581 tokens per use (tool 2,286 · guidance 553 · typical payload 742)How token costs are estimated

Example: Map

Plan a 10-day Pacific Northwest road trip.

Your 10-day Pacific Northwest road trip

  1. Day 1 · Seattle
    1. Seattle, City
  2. Day 2 · Olympic National Park
    1. Port Angeles, Harbor town
    2. Hurricane Ridge, Scenic viewpoint
  3. Day 3 · Rain forest
    1. Hoh Rain Forest, Rain forest
  4. Day 4 · Pacific coast
    1. Ruby Beach, Beach
    2. Lake Quinault, Lake
  5. Day 5 · Astoria
    1. Astoria, Port town
  6. Day 6 · Oregon coast
    1. Cannon Beach, Beach
  7. Day 7 · Portland
    1. Portland, City
  8. Day 8 · Columbia River Gorge
    1. Multnomah Falls, Waterfall
  9. Day 9 · Mount Rainier
    1. Mount Rainier · Paradise, National park
  10. Day 10 · Back to Seattle
    1. Snoqualmie Falls, Waterfall

When to use

  • Where things are matters to the decision: viewings across a city, hotels near a venue, a route between stops.
  • The user needs to choose a place, or tell you a location by pointing at it, like a pothole or a meeting point.
  • A trip over several days: itinerary shows the stops by day on a route, with a card per place.
  • Somewhere to stay near a named place (a landmark, a venue, a station, an address), from a lodging search with coordinates and prices: nearby shows which stays are actually close and what being closer costs.

When not to use

  • There's one address. Write it out.
  • Location doesn't affect the decision. Use a list or a Data Table.
  • You don't have coordinates from a tool. Never guess latitude or longitude.
  • Hotels with no place to stay near ("hotels in Paris"): use Entity Cards. A hotel the user has already chosen: use Hotel Booking for its rooms.

How the agent should use it

  • Take coordinates from a tool result, never from memory.
  • Pick the mode for the job: view to show places, pick when the user must choose one of them, pin when they must point at a spot, itinerary for a trip over several days, nearby for places to stay around a landmark or venue.
  • With route, give every place an order starting at 1. Markers are numbered and joined by a line in that order.
  • Keep descriptions to the one fact that helps the choice: price, distance, time.
  • In itinerary, send stops in visiting order with a day each and a kind when one fits. Pass route: { coordinates } only when a routing tool gave you a real path. Set selectable only when you need the user to choose a stop.
  • In nearby, the anchor comes from a geocoder and every stay's coordinates and nightly price from the search. Mark up to 5 picks with the reason that is true in the data: closest and lowest-price only on the stay that is; best-value and top-rated are the agent's call. Send picks first, then other stays from the same search (up to 60 in all). Give travel or path only when a routing tool returned them.
  • On view-rooms, show that hotel's rooms with Hotel Booking. On refine, run the search again with that filter and send a new call.
  • On cancel, do nothing and ask what they'd like instead.
  • Lifecycle: places and trips don't expire. If ratings or opening states (a stop's hours) come from live data, set fetchedAt and staleAfterMs.

Modes

ModeWhat the user doesResult
viewLooks. Markers and list rows open a popup with the place's detailsNone
pickSelects a place by its marker or its list row, then confirms{ action: "select", placeId }
pinClicks or taps the map to drop a pin, drags it to adjust, then confirms{ action: "pin", lat, lng }
itineraryFilters by day, browses stops on the map and in the card stripNone, or { action: "select-stop", stopId } with selectable
nearbyBrowses picks and other stays around the anchor, opens one's rooms or narrows the search{ action: "view-rooms", stayId } or { action: "refine", refinementId }

pick, pin and a selectable itinerary can also return { action: "cancel" }.

Itinerary

The map fills the card. Stops are joined in order by a line (straight legs, or route.coordinates when given) and drawn as round markers with an icon for their kind. A strip of place cards sits on the map: thumbnail, name, rating and category, then the day and opening state.

  • Day filter: "All locations" or one day. Picking a day fits the map to that day's stops, dims the others and highlights the day's leg, including the drive in from the previous stop. The strip shows that day's cards.
  • Selecting a stop: click its marker or its card. The card scrolls into view, the marker grows and the map eases to it (it jumps with reduced motion). Clicking a dimmed stop switches the filter to its day.
  • Fit all stops: the round button next to the filter brings every stop back into view. It never asks for the user's location.
  • Expand: opens the same map fullscreen. Escape or Close returns to the card.
  • Labels: to keep the map readable, names show only for the selected stop, city stops, and the trip's first and last stop. Every other stop is named on its card.
  • Result: display only by default; selection stays local. With selectable, the user confirms a stop and the agent receives { action: "select-stop", stopId }.

Nearby

Places to stay around one place the user named. The map is light in both themes (the plain Positron style), so it reads the same in a dark chat; its pins and route keep light-theme colours, while the stay card on top follows the page's theme.

ElementBehavior
AnchorThe place the user named, as a blue pill with its name. Every distance is measured to it
PicksPrice pills with a blue dot. The map opens fitted to the anchor and the picks, with the first pick selected
Other staysSmall dots: context from the same search. Selecting one shows its card with Back to picks
Stay cardOn the map: photo, name and stars, the pick's reason ("Closest to Eiffel Tower", "Best value", "Lowest price", "Top rated", or "In this search"), the nightly price, and the distance to the anchor. The pager steps through the picks. View rooms sends the stay
DistanceThe routed travel time when given ("6 min walk to Eiffel Tower"), else the straight-line distance in km or miles ("950 m to Eiffel Tower"). The map draws the route, or a dashed straight line, from the selected stay to the anchor and fits both in view
StripEvery stay below the map: picks first, then the rest nearest first. Selecting a tile selects it on the map, and a stay selected on the map scrolls its tile into view
RefinementsChips under the strip, with counts. One tap sends it back to the agent

The user can browse freely; only View rooms and a refinement send a result. After one, the card locks: the chosen button reads Rooms opened (or the chosen chip shows a check), and the others say why they're disabled. While streaming, the anchor appears first, then stays one at a time; actions wait for the whole call. The helpers in map-utils.ts (distanceMeters, distanceLine, stripOrder, picksOf, searchLine) are pure, so the server can reuse them, for example to check that a "closest" pick really is.

Accessibility

The list below the map is the full alternative to the map: every place is listed with its name and description, and in pick mode each row has its own select button. Markers are buttons named after their place, so they can be reached with Tab. In pin mode, "Drop pin at map center" places the pin without a pointer (pan the map with the arrow keys first), the pin moves with the arrow keys (Shift for bigger steps), and its coordinates are shown as text.

In itinerary mode the markers are hidden from assistive tech. A visually hidden ordered list gives every stop by day, with its category, rating and opening state. The card strip is a labelled region of buttons: Tab reaches it, the arrow keys (and Home/End) move between cards, and each card reports whether it's selected. Day changes are announced. The fullscreen view is a modal dialog that keeps focus inside and returns it to Expand when it closes.

In nearby mode the markers are hidden from assistive tech too; the strip is the full alternative. Each tile is a button named with everything the card shows (name, stars, pick reason, price and distance) and reports whether it's selected; the arrow keys and Home/End move between tiles. Selecting a stay is announced, with its place among the picks.

Map tiles

The map uses MapLibre GL with CARTO's free styles, which need no API key. The itinerary follows the page's .dark class (Positron or Dark Matter); view, pick and pin stay on the light Voyager style, and nearby on the light Positron style, in both themes. The attribution stays visible as the tile provider requires. Scroll-wheel zoom needs Ctrl or ⌘ so the map doesn't trap page scrolling in a chat.

MapLibre runs its tile parsing in a web worker. Bundlers don't copy that worker for you, so the component points MapLibre at the matching published build on jsDelivr. To serve it yourself, copy node_modules/maplibre-gl/dist/maplibre-gl-worker.mjs to your public folder and call setWorkerUrl("/maplibre-gl-worker.mjs") from maplibre-gl before the map renders.

Installation

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

Usage

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

const tools = {
  [mapTool.name]: tool({
    description: mapTool.description,
    inputSchema: mapTool.inputSchema,
  }),
}

In view mode, and in itinerary without selectable, the map is the whole answer, so give the tool an execute that returns immediately. In pick, pin, selectable itinerary and nearby modes, the agent receives:

{ action: "select", placeId: "casa-tejo" }
{ action: "pin", lat: 38.72683, lng: -9.13518 }
{ action: "select-stop", stopId: "hoh" }
{ action: "view-rooms", stayId: "villa-grenelle" }
{ action: "refine", refinementId: "under-150" }
{ action: "cancel" }

With LangGraph

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

Restoring from history

Pass the stored tool output as result. The map restores the selected place or the pin, locks, and never calls onResult again:

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

Schema

PropTypeNotes
titlestring
mode"view" | "pick" | "pin" | "itinerary" | "nearby"Default view
places{ id, name, lat, lng, description?, category?, order? }[]Up to 25. Data-bound. Optional in pin mode
routeboolean | { coordinates?: [lng, lat][] }true: number the markers and join them in order. An object follows a detailed path. Default false
stops{ id, name, lat, lng, day?, kind?, image?, rating?, category?, hours?, url? }[]itinerary only. Up to 40, in visiting order. Data-bound
days{ day, label? }[]itinerary only. Labels for the day filter
selectablebooleanitinerary only. Let the user choose a stop. Default false
anchor{ name, lat, lng }nearby only. The place to stay near. Data-bound
stays{ id, name, lat, lng, price, stars?, image?, travel?, path?, pick? }[]nearby only. Up to 60, picks first. Data-bound
currencystringnearby only. ISO 4217, e.g. GBP
search{ location?, checkIn?, checkOut?, adults?, total? }?nearby only. The line under the title: "1,624 stays in Paris · 14–19 Nov · 5 nights · 2 adults"
refinements{ id, label, count? }[]?nearby only. Up to 4
units"metric" | "imperial"nearby only. Default metric
center{ lat, lng }?Initial center. Omit to fit the map to the places
zoomnumber?Initial zoom with center, 1–19
submitLabelstring?Confirm button for pick, pin and selectable itinerary

A stop's kind is one of beach, mountain, city, forest, lake, waterfall, harbor, museum, food, hotel, viewpoint or activity; each has its own marker icon. image is { src, alt } with an http(s) or data:image URL, and url must be http(s). A stop's hours is its opening state, like "Open" or "Closes 6 PM".

Host props: status (streaming, ready, submitted, cancelled), onResult, result and className.

Source

On this page