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.
≈ 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
- Day 1 · Seattle
- Seattle, City
- Day 2 · Olympic National Park
- Port Angeles, Harbor town
- Hurricane Ridge, Scenic viewpoint
- Day 3 · Rain forest
- Hoh Rain Forest, Rain forest
- Day 4 · Pacific coast
- Ruby Beach, Beach
- Lake Quinault, Lake
- Day 5 · Astoria
- Astoria, Port town
- Day 6 · Oregon coast
- Cannon Beach, Beach
- Day 7 · Portland
- Portland, City
- Day 8 · Columbia River Gorge
- Multnomah Falls, Waterfall
- Day 9 · Mount Rainier
- Mount Rainier · Paradise, National park
- Day 10 · Back to Seattle
- 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:
itineraryshows 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:
nearbyshows 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:
viewto show places,pickwhen the user must choose one of them,pinwhen they must point at a spot,itineraryfor a trip over several days,nearbyfor places to stay around a landmark or venue. - With
route, give every place anorderstarting 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, sendstopsin visiting order with adayeach and akindwhen one fits. Passroute: { coordinates }only when a routing tool gave you a real path. Setselectableonly 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:closestandlowest-priceonly on the stay that is;best-valueandtop-ratedare the agent's call. Send picks first, then other stays from the same search (up to 60 in all). Givetravelorpathonly when a routing tool returned them. - On
view-rooms, show that hotel's rooms with Hotel Booking. Onrefine, 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, setfetchedAtandstaleAfterMs.
Modes
| Mode | What the user does | Result |
|---|---|---|
view | Looks. Markers and list rows open a popup with the place's details | None |
pick | Selects a place by its marker or its list row, then confirms | { action: "select", placeId } |
pin | Clicks or taps the map to drop a pin, drags it to adjust, then confirms | { action: "pin", lat, lng } |
itinerary | Filters by day, browses stops on the map and in the card strip | None, or { action: "select-stop", stopId } with selectable |
nearby | Browses 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,
citystops, 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.
| Element | Behavior |
|---|---|
| Anchor | The place the user named, as a blue pill with its name. Every distance is measured to it |
| Picks | Price pills with a blue dot. The map opens fitted to the anchor and the picks, with the first pick selected |
| Other stays | Small dots: context from the same search. Selecting one shows its card with Back to picks |
| Stay card | On 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 |
| Distance | The 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 |
| Strip | Every 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 |
| Refinements | Chips 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
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
| Prop | Type | Notes |
|---|---|---|
title | string | |
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 |
route | boolean | { 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 |
selectable | boolean | itinerary 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 |
currency | string | nearby 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 |
zoom | number? | Initial zoom with center, 1–19 |
submitLabel | string? | 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.