# Next.js Vector Map

A Next.js vector map for the App Router: jsVectorMap in a client component, fed marker data from a server component page, with colors that follow dark mode.

A map with markers tells people at a glance where your offices, sales or visitors are. `VectorMap` draws such a map as interactive SVG, with zoom buttons and markers in the colors of your theme. It is built on [jsVectorMap](https://github.com/themustafaomar/jsvectormap) 1.7.

The map ships with the free and PRO versions of [AdminKit for Next.js](/nextjs/). Both have the world map, and AdminKit PRO also has a map of the United States.

jsVectorMap needs the browser, so `VectorMap` is a client component, a file that starts with `"use client"`. On the server and in the static export, it renders a skeleton and an empty `<div>`. After hydration, when the browser has taken over the HTML that Next.js built, an effect imports jsVectorMap and the map data and draws the map. You don't need `next/dynamic`. The component takes a function of the theme colors instead of fixed options, like the [chart components](/nextjs/docs/charts/overview/#vector-maps).

## Example

`options` is a function, and a server component cannot pass a function to a client component. So write a small client component that owns the options and takes the data as props. The demo's `MarkerMap` does this, in `src/components/pages/maps/vector/marker-map.tsx`:

```html
<div id="vector-map-world" style="height:350px"></div>

<script>
adminkit.vectorMap(function (theme) {
  return {
    selector: "#vector-map-world",
    map: "world",
    zoomButtons: true,
    zoomOnScroll: false,
    markers: [
      { coords: [51.507351, -0.127758], name: "London" },
      { coords: [40.7127837, -74.0059413], name: "New York" },
      { coords: [35.689487, 139.691711], name: "Tokyo" },
      { coords: [6.524379, 3.379206], name: "Lagos" },
      { coords: [19.075983, 72.877655], name: "Mumbai" }
    ],
    markerStyle: {
      initial: { r: 9, stroke: theme.white, strokeWidth: 7, fill: theme.primary },
      hover: { fill: theme.primary, stroke: theme.primary }
    },
    regionStyle: {
      initial: { fill: theme["gray-200"] }
    }
  };
});
</script>
```
```tsx code title="src/components/marker-map.tsx"
"use client";

import { VectorMap } from "@/components/plugins/vector-map";

export function MarkerMap({
  map,
  markers,
}: {
  map: "world" | "us_aea_en";
  markers: { coords: [number, number]; name: string }[];
}) {
  return (
    <div className="position-relative" style={{ height: 350 }}>
      <VectorMap
        map={map}
        className="h-100"
        options={(theme) => ({
          zoomButtons: true,
          zoomOnScroll: false,
          markers,
          markerStyle: {
            initial: { r: 9, stroke: theme.white, strokeWidth: 7, fill: theme.primary },
            hover: { fill: theme.primary, stroke: theme.primary },
          },
          regionStyle: {
            initial: { fill: theme["gray-200"] },
          },
        })}
      />
    </div>
  );
}
```

The page passes plain data, so it stays a server component:

```tsx title="src/app/(dashboard)/maps-vector/page.tsx"
import { MarkerMap } from "@/components/marker-map";
import { pageMetadata } from "@/lib/site";

export const metadata = pageMetadata("/maps-vector");

const offices: { coords: [number, number]; name: string }[] = [
  { coords: [51.507351, -0.127758], name: "London" },
  { coords: [40.7127837, -74.0059413], name: "New York" },
  { coords: [35.689487, 139.691711], name: "Tokyo" },
  { coords: [6.524379, 3.379206], name: "Lagos" },
  { coords: [19.075983, 72.877655], name: "Mumbai" },
];

export default function Page() {
  return (
    <div className="card">
      <div className="card-body">
        <MarkerMap map="world" markers={offices} />
      </div>
    </div>
  );
}
```

The `position-relative` wrapper with a height sizes both the skeleton and the map. `className="h-100"` makes the map fill it.

## Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `map` | `"world" \| "us_aea_en"` | | The map; `"us_aea_en"` is PRO only |
| `options` | `(theme: Theme) => VectorMapOptions` | | [jsVectorMap's options](https://jvm-docs.vercel.app/), without `selector` and `map` |

`id`, `className`, `aria-*` and the other `<div>` props go to the map element. The component is in `src/components/plugins/vector-map.tsx`.

## Colors and dark mode

The `theme` argument holds the colors of the current mode, such as `theme.primary` and `theme["gray-200"]`. [Dark mode](/nextjs/docs/customization/dark-mode/#theme-colors) lists them. The theme is only known in the browser, which is one more reason the map draws after hydration. When the mode changes, the component calls `options` again and applies its `regionStyle.initial` and `markerStyle.initial`. The rest of the options, such as `markers`, are read once, when the map is created.

The component resizes the map with the window. It destroys the map when it leaves the page, for example when you navigate to another page.

## United States map

> This map is only available in **AdminKit PRO**. [Learn more](/pricing/#nextjs).

For data about the US, a map of the states says more than the world map. `map="us_aea_en"` draws them, as on the e-commerce dashboard and the vector maps demo:

```html
<div id="vector-map-usa" style="height:350px"></div>

<script>
adminkit.vectorMap(function (theme) {
  return {
    selector: "#vector-map-usa",
    map: "us_aea_en",
    zoomButtons: true,
    zoomOnScroll: false,
    markers: [
      { coords: [37.77, -122.41], name: "San Francisco: 375" },
      { coords: [40.71, -74.0], name: "New York: 350" },
      { coords: [32.77, -96.79], name: "Dallas: 225" }
    ],
    markerStyle: {
      initial: { r: 9, stroke: theme.white, strokeWidth: 7, fill: theme.primary },
      hover: { fill: theme.primary, stroke: theme.primary }
    },
    regionStyle: {
      initial: { fill: theme["gray-200"] }
    }
  };
});
</script>
```
```tsx code
<MarkerMap
  map="us_aea_en"
  markers={[
    { coords: [37.77, -122.41], name: "San Francisco: 375" },
    { coords: [40.71, -74.0], name: "New York: 350" },
    { coords: [32.77, -96.79], name: "Dallas: 225" },
  ]}
/>
```

## Other maps

You can add more maps from jsVectorMap's collection. Each map is a file in `src/components/plugins/vector-maps/` that registers itself with jsVectorMap. To add one, copy its file there, with a `.d.ts` file like the others. Then add an entry to the `maps` object in `vector-map.tsx`. Its key is the name the file passes to `jsVectorMap.addMap()`.

## Accessibility

- The SVG map has no text alternative. Show the same data as text too, for example a list of the markers.
- `zoomOnScroll: false` keeps the page scrolling when the pointer is over the map.
- The skeleton is `aria-hidden="true"`.

## Related

- [React Vector Map](/react/docs/plugins/vector-map/): the same component in AdminKit for React (Vite)
- [Bootstrap 5 vector map](/bootstrap/docs/plugins/vector-map/): the same plugin in the HTML version
- [Bootstrap 5 dark mode](/bootstrap/docs/getting-started/dark-mode/): `adminkit.vectorMap()` in the HTML version
- [Charts](/nextjs/docs/charts/overview/#vector-maps): the chart components that share the theme function
- [Vector maps demo](https://nextjs-demo.adminkit.io/maps-vector)

---

Canonical HTML page: https://adminkit.io/nextjs/docs/plugins/vector-map/

Site overview for agents: https://adminkit.io/llms.txt (expanded: https://adminkit.io/llms-full.txt) · Documentation: https://adminkit.io/bootstrap/docs/ · Questions: support@adminkit.io
