Markers on a projected sheet — stores, field jobs, vehicles, live visitors — without a tile provider or API key. Pins sit in Web Mercator inside your bounds over a quiet coordinate grid with a scale bar; pass a backdrop to lay them over your own static map. Selection syncs with a list, and arrow keys move to the nearest pin in that direction.
Moda
Installation
The whole kit as one package — get updates by bumping the version. Add the styles to globals.css: @import "@vamos-ui/react/styles.css";
Usage
import { PinMap } from "@vamos-ui/react";
<PinMap aria-label="Stores" pins={stores.map((s) => ({ id: s.id, lat: s.lat, lng: s.lng, label: s.name }))}
selectedId={selected} onSelectedIdChange={setSelected} />Keyboard: Tab enters on the selected (or first) pin, arrows go to the nearest pin in that direction, Home/End jump, Enter/Space toggles selection, Escape clears.
Not a slippy map: no panning or zoom. For street-level navigation use a map provider and keep PinMap for overviews and placeholders.
Examples
Dispatch with a synced list (ops)
Status tones, a pulse for vehicles en route, icons in the markers, and selection shared with the list.
Measure for curtains, Üsküdar
Live visitors (analytics)
variant="bubble" sizes by value (area, not radius); children overlay the map.
Right now
177
Labels and dots
Without bounds the map fits every pin; showLabels for a handful of sites.
API
PinMap
| Prop | Type | Default |
|---|---|---|
pins{ id, lat, lng, label, description?, tone?, value?, pulse?, icon? }. label is the accessible name. | MapPin[] | — |
aria-labelRequired. What the map shows. | string | — |
boundsVisible area. Omit to fit every pin. Always widened to the aspect ratio so shapes stay true. | { north, south, west, east } | — |
aspectRatioWidth ÷ height. | number | 16 / 10 |
variantMarkers, small dots or value-sized bubbles. | "pin" | "dot" | "bubble" | "pin" |
selectedId / defaultSelectedId / onSelectedIdChangeSelection (controlled or not). Clicking a selected pin clears it; Escape too. | string | null | — |
calloutCard above the selected pin. Default: label + description; it also becomes the pin's accessible description. | false | (pin) => ReactNode | — |
showLabelsLabels beside every marker. | boolean | — |
backdropYour own layer under the pins (a static map image, district outlines). | ReactNode | — |
bareHide coordinate ticks and the scale bar. | boolean | — |
childrenOverlay content (legend, totals), positioned by you. | ReactNode | — |