Skip to content

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.

npx shadcn@latest registry add @unmap=https://registry.unmap.dev/r/{name}.json
npx shadcn@latest add @unmap/map

The 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.dev

There are two lanes, one per CLI, with the same items:

LaneCLIWhat you get
Reactshadcn.tsx components in components/ui/. The geocoder and routing panel are your own shadcn Combobox, Field, Input, Button and Alert.
Vueshadcn-vueUnmap*.vue components, one directory per item under components/ui/, on your shadcn-vue Combobox, Field, Input, Button and Alert.

Each family of items has its own page:

PageItems
Map and markersmap-context, map-themes, map, marker-popup, map-controls
Data layersmap-layers, route-line, geojson-layer, cluster-layer, heatmap-layer, arc-layer, flow-layer, layer-legend
Search and routinggeocoder, routing-panel, route-steps
Overlay componentsenergy-layer-control, energy-map, well-search, legal-land-search, energy-popup, treaty-context
Map toolsmeasure, feature-inspector, time-slider, draw, compare-swipe, elevation-profile, api-calls, api-inspector
Blocksstore-locator, directions, flow-map, elevation-explorer, delivery-tracker, census-choropleth, land-treaty-inspector, address-playground, energy-explorer

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 geocoder

add 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-popup

shadcn-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-popup

Every 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. The React items expect a shadcn project on Base UI, the base-nova style that npx shadcn init --base base --preset nova sets up and unmap create uses: the geocoder is Base UI's Combobox, and the map controls hand your Button to the Tooltip through Base UI's render prop rather than Radix's asChild. 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:

live result
the code that produced it
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, UnmapPopup.vue, UnmapMarkerTooltip.vue and UnmapMarkerLabel.vue, plus lib/map-context/index.ts, under src/ or Nuxt 4's app/ as your aliases say. The import is @/lib/map-context.

<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", "pastel", "atlas" or "swiss") 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-lg and bg-muted, so it follows your --radius and 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, --ring and --muted-foreground by CSS the map item 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 through font: inherit. The chrome follows your .dark class.
  • Markers and route lines default to your --primary, resolved to a concrete colour when they are drawn and again after every style swap, because MapLibre paint cannot read var().
  • 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. The attribution stays either way. For buttons on your own primitives, add map-controls, described in Map and markers.
  • The map is a globe by default, flattening into Mercator as it zooms in. <Map projection="mercator"> draws the flat map at every zoom.
  • The basemap follows your page's light or dark mode: a dark or light class on <html>, then data-theme, then color-scheme, then the reader's system setting. <Map mode="light"> or mode="dark" pins it. Switching mode, style, theme or lang swaps the style in place, so the camera, markers, popups and a drawn route stay where they are.
  • Layers you add yourself should add themselves again when useMapGeneration() changes, or when useMapReady() turns true again: a swap removes every source and layer.
  • center, zoom, bearing and pitch move the camera in place. Pass onViewportChange (Vue: @viewport-change) and feed the values back for a controlled map; moves caused by these props are not reported back, so it cannot loop.
  • Your Spinner covers the map until its first style loads. <Map loading> shows it on demand, for example while your own data loads.

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, data-unmap-routing or data-unmap-legend attribute, and every popup carries the unmap-popup class, as hooks for your own CSS.

Templates and examples

Map templates are complete React experiences distributed as registry:block items. They copy editable components and English/French sample data into your app, install their dependencies, and inherit your shadcn theme. The four templates use Base UI shadcn primitives.

Once the @unmap namespace above is configured:

npx shadcn@latest add @unmap/business-locator-01
npx shadcn@latest add @unmap/local-guide-01
npx shadcn@latest add @unmap/public-notices-01
npx shadcn@latest add @unmap/map-story-01

Or with the unmap CLI, which also works for the examples: npx @unmap/cli add business-locator-01.

You can also install directly, without namespace configuration:

npx shadcn@latest add https://registry.unmap.dev/r/business-locator-01.json
import { BusinessLocator } from "@/components/unmap-business-locator"
 
<BusinessLocator apiKey="um_live_..." />

Pass your browser key and allow your site's origin on it. Use lang="fr" for French. Edit lib/business-locator-01-data.ts to replace the sample places, and the copied components to change the layout. The other exports are LocalGuide, PublicNotices and MapStory, in components/unmap-local-guide.tsx, components/unmap-public-notices.tsx and components/unmap-map-story.tsx.

MapStory follows the reader's scroll, with its chapters in lib/map-story-01-data.ts: as each chapter reaches the middle of the screen the camera flies to it, and a chapter with a route or travel-time areas asks the routing API for them once, the first time it opens (one request each). LocalGuide steps through its stops with Previous stop and Next stop buttons instead.

Smaller examples ship the same way: one item each, React-only, installed with npx @unmap/cli add <name>. They are listed in the gallery and shown beside each item on its family page, such as Map tools.

Templates and examples ship in the React lane only; the page blocks install in both. The business locator, local guide and public notices also have standalone HTML starters and GeoJSON downloads on their template pages.

For agents

registry.unmap.dev/llms.txt indexes every item for coding agents, and each item has a Markdown page with its install command, props and a working example: registry.unmap.dev/llm/<item>.md for React and registry.unmap.dev/llm/vue/<item>.md for Vue and Nuxt. npx @unmap/cli add prints the pages for the items it installs. A prompt that works in a project with a components.json:

Read https://registry.unmap.dev/llms.txt. Then run npx @unmap/cli add map in this project and render the map on the home page, centred on Calgary, with my unmap key from the environment.

Next steps