Components
registry.unmap.dev is a shadcn-format component registry for unmap
maps. Components are copied into your project, not installed as a package, so you own the source
and can edit it freely. They install the way every shadcn component does: register the @unmap
registry once, then add by name. Files land in components/ui/ and lib/ beside your own, and
the CLI asks before overwriting anything.
map)npx shadcn@latest registry add @unmap=https://registry.unmap.dev/r/{name}.json
npx shadcn@latest add @unmap/mapnpx shadcn-vue@latest add https://registry.unmap.dev/r/vue/map.jsonThe React line registers the registry once per project, so later items can be named @unmap/<item>; shadcn-vue has no registry add and takes the item URL directly. Files land in components/ui/ beside your own; nothing is overwritten without asking.
Browse every item on registry.unmap.devThere are two lanes, one per CLI, with the same items:
| Lane | CLI | What you get |
|---|---|---|
| React | shadcn | .tsx components in components/ui/. The geocoder and routing panel are your own shadcn Combobox, Field, Input, Button and Alert. |
| Vue | shadcn-vue | Unmap*.vue components, one directory per item under components/ui/, on your shadcn-vue Combobox, Field, Input, Button and Alert. |
| Item | What it is |
|---|---|
map | The map display component. Mounts a container, creates a MapLibre map on the unmap gateway, and shares it with its children. Restyles MapLibre's popups and controls onto your tokens. |
geocoder | A search box with debounced autocomplete that flies the map to the chosen place. |
routing-panel | From/to inputs that resolve both addresses, request a route, and draw it on the map. |
marker-popup | Declarative markers and popups positioned on the map. |
map-themes | Named short theme presets (u00 and up) for the map's theme prop. Compose your own at unmap.dev/create. |
map-context | The shared map store the other items depend on. Installed automatically. |
energy-map | <Map> already composed with profile="energy" (muted by default). Map composition only, not a style. |
energy-layer-control | Toggles the energy overlay set on a map that already used profile="energy". |
well-search | Search box labelled for UWIs. Sends the query string only; the gateway detects UWI vs civic. |
legal-land-search | Search box labelled for LSD. Same rule: query string only. |
energy-popup | Identify wells, pipelines, and facilities on click. |
The energy items pair with profile="energy". See Energy. legal-land-search is the LSD twin of well-search.
Start a new project
@unmap/cli scaffolds a shadcn or shadcn-vue project with the components already in it:
npx @unmap/cli create my-app --key um_live_...It runs npx shadcn init (or shadcn-vue init for --framework nuxt and vue) with your
template and preset, registers @unmap, adds the four components, writes the key to the
framework's public env file, and replaces the home page with a map, a search box and a pin.
Pass --theme <code> from /create and it asks whether markers and routes take the
theme's accent or your design system's --primary.
Add to an existing project
npx @unmap/cli add map geocoderadd reads your package.json to pick the lane, writes the @unmap entry into components.json
itself if it is not there yet, and hands off to the shadcn CLI. By hand it is two commands on
React and one on Vue and Nuxt:
npx shadcn@latest registry add @unmap=https://registry.unmap.dev/r/{name}.json
npx shadcn@latest add @unmap/map @unmap/geocoder @unmap/routing-panel @unmap/marker-popupnpx shadcn-vue@latest add https://registry.unmap.dev/r/vue/map.jsonshadcn-vue has no registry add command yet. To write @unmap/map instead of a URL, add the
registry to components.json once (unmap add does this for you):
{ "registries": { "@unmap": "https://registry.unmap.dev/r/vue/{name}.json" } }npx shadcn-vue@latest add @unmap/map @unmap/geocoder @unmap/routing-panel @unmap/marker-popupEvery map-bound component depends on map-context, so adding one fetches it. The geocoder brings
the official Combobox; the routing panel brings Field, Input, Button and Alert. Preview a change
with --dry-run before it touches a file (shadcn also takes --diff; shadcn-vue 2.8 supports
neither yet, so on the Vue lane unmap add --dry-run only prints what it would run).
npx shadcn search @unmap (or npx shadcn-vue search @unmap, and view @unmap/map for
one item's detail) lists every item once the namespace is registered.
React
import { Map } from "@/components/ui/map";
import { Geocoder } from "@/components/ui/geocoder";
import { Marker, Popup } from "@/components/ui/marker-popup";
import { MAP_THEMES } from "@/lib/map-themes";
export default function Demo() {
const apiKey = process.env.NEXT_PUBLIC_UNMAP_KEY!;
return (
<div style={{ height: 480 }}>
<Map apiKey={apiKey} center={[-114.07, 51.05]} zoom={11} theme={MAP_THEMES.boreal} accent="system">
<div style={{ position: "absolute", top: 12, left: 12, width: 280, zIndex: 10 }}>
<Geocoder apiKey={apiKey} />
</div>
<Marker lngLat={[-114.07, 51.05]}>
<Popup>Downtown Calgary</Popup>
</Marker>
</Map>
</div>
);
}The map those components mount is the one @unmap/maps builds, and it looks like this:
import { Unmap } from "@unmap/sdk";
const unmap = new Unmap({
key: "um_live_...",
container: "map",
style: "base",
mode: "light",
lang: "en",
center: [-114.07, 51.05],
zoom: 11,
});If you would rather not copy components, @unmap/react and @unmap/next provide the same map as
hooks and a ready-made component. See the Overview.
Vue and Nuxt
The Vue lane installs through shadcn-vue into a project that has run npx shadcn-vue init.
Files land one directory per item, each with an index.ts: components/ui/map/UnmapMap.vue,
geocoder/UnmapGeocoder.vue, routing-panel/UnmapRoutingPanel.vue, marker-popup/UnmapMarker.vue
and UnmapPopup.vue, plus lib/map-context/index.ts, under src/ or Nuxt 4's app/ as your
aliases say. The import stays @/lib/map-context.
Upgrading from the flat components/ui/Unmap*.vue layout? Delete those files after re-adding; the
new directories sit beside them. Also delete any lib/vue/ directory or flat lib/map-context.ts
file left by an install from before 2026-09-07: a flat file shadows the new
lib/map-context/index.ts, since TypeScript and Vite resolve the file before the directory index.
<script setup lang="ts">
import { UnmapMap } from "@/components/ui/map";
import { UnmapGeocoder } from "@/components/ui/geocoder";
import { UnmapMarker, UnmapPopup } from "@/components/ui/marker-popup";
const apiKey = import.meta.env.VITE_UNMAP_KEY;
</script>
<template>
<div style="height: 480px">
<UnmapMap :api-key="apiKey" :center="[-114.07, 51.05]" :zoom="11" theme="u4e" accent="system">
<div style="position: absolute; top: 12px; left: 12px; width: 280px; z-index: 10">
<UnmapGeocoder :api-key="apiKey" />
</div>
<UnmapMarker :lng-lat="[-114.07, 51.05]">
<UnmapPopup>Downtown Calgary</UnmapPopup>
</UnmapMarker>
</UnmapMap>
</div>
</template>UnmapMap takes the style as map-style ("base", "muted", "outdoor", "blueprint", "blush", "orchid", "canopy", "lagoon", "tropic", "sunset", "bold" or "pastel") so the plain
style attribute keeps styling the container; class and style fall through to it. It also
accepts mode ("light" or "dark") and lang ("en" or "fr"). Component names keep the
Unmap prefix because <map> is an HTML element. On Nuxt, shadcn-nuxt reads each directory's
index.ts and auto-imports the components as <UiUnmapMap>, <UiUnmapGeocoder> and so on; set
shadcn: { prefix: "" } in nuxt.config.ts to use them as <UnmapMap>.
Not on shadcn-vue? @unmap/vue and @unmap/nuxt provide the map, geocoder and routing as
composables for any UI. See the Overview.
Styling and theming
The registry ships no theme and no brand colour. Components take on your design system:
- The map container uses
rounded-lgandbg-muted, so it follows your--radiusand palette. - The geocoder and routing panel are your own shadcn primitives.
- MapLibre's popups, zoom and compass buttons, attribution and scale bar are restyled onto
--popover,--background,--border,--radius,--ringand--muted-foregroundby CSS themapitem adds to your stylesheet on install, scoped under[data-unmap-map]so other maps in your app are untouched; popups and controls take the page's font throughfont: inherit. Dark mode follows your.darkclass. - Markers and route lines default to your
--primary, resolved to a concrete colour when they are created, because MapLibre paint cannot readvar(). - The zoom and compass buttons are on by default, at
top-right.<Map controls={false}>takes them off, and<Map controls={{ position: 'bottom-left' }}>moves them. <Map projection="globe">draws the map as a sphere. The default ismercator, the flat map.
Cartography is separate from UI chrome. The theme prop takes a short code such as u4e or a full
ut1. code from the theme builder; that code carries its own marker and route colours.
<Map accent="theme" | "system"> says which wins: the default is "theme" when a theme is set
and "system" otherwise, and the export dialog on /create asks you before it writes
the snippet. An explicit color or lineColor prop always wins over both. See
Maps API.
Every component marks its root with a data-unmap-map, data-unmap-geocoder or
data-unmap-routing attribute, and every popup carries the unmap-popup class, as hooks for your
own CSS.
Next steps
- Browse every item with install commands at registry.unmap.dev.
- Quickstart for the plain-JavaScript path with
@unmap/sdk. - Geocoding API and Routing API for what the geocoder and routing panel call under the hood.
- Energy for
energy-map,well-search,energy-popup, andenergy-layer-control.