Vue

vue-apexmaps is built for Vue's own change model rather than adapted from the React wrapper. A React caller replaces options with a new object; a Vue caller mutates reactive state in place, so the last-applied options and the current props are frequently the same object. The component keeps a structural snapshot instead of a reference so that still gets noticed.

Installation

npm install apexmaps vue-apexmaps

apexmaps and vue (>=3.3.0) are peer dependencies, so there is one copy of each in your app.

Usage

<script setup>
import ApexMaps from 'vue-apexmaps'
import 'apexmaps/apexmaps.css'

const options = { geo: { map: 'world' } }
const series = [
  {
    type: 'choropleth',
    name: 'Coverage',
    data: [{ key: 'IND', value: 42 }],
    scale: { palette: 'blues', classes: 5 },
  },
]
</script>

<template>
  <ApexMaps :options="options" :series="series" :height="480" @feature-click="onClick" />
</template>

Props

PropTypeNotes
optionsApexMapsOptionsRequired. The same options object the core takes.
seriesSeries[]Optional shorthand for options.series. Takes precedence over it; both routes reach updateSeries(), so both tween.
width / heightnumber | stringShorthand for options.chart.width / .height. See Sizing below.

Anything else you bind (class, style, id, aria-*) falls through to the outer element.

Events

One event per core event, emitted under its own name, so both @feature-click and @featureClick work in a template: rendered, updated, resized, feature-click, feature-hover, feature-focus, mark-click, mark-hover, cluster-click, drilldown, drillup, selection-change, legend-toggle, zoom, pan-end.

Payload shapes for each event are documented on API: Methods and Events.

The imperative API

Camera moves, drilldown, export, and diagnostics are methods, not options. Reach them through a template ref:

<script setup>
import { useTemplateRef } from 'vue'
const el = useTemplateRef('mapEl')
const zoomToIndia = () => el.value.map.frameFeature('IND')
</script>

<template>
  <ApexMaps ref="mapEl" :options="options" />
</template>

Reactivity

Three things follow from the component being built for Vue's own change model:

Mutating in place works. options.legend.position = 'top' on reactive state is seen, because the component keeps a structural snapshot of the last-applied options rather than a reference to them. A reference would be the very object you just mutated, so every comparison would say nothing changed and the map would never update.

Reactive geometry never reaches the map. reactive() creates proxies lazily as an object is read, so a topology handed to the core would be proxied feature by feature and coordinate by coordinate as ingest walks it: tens of thousands of proxies, and a trap on every read after that. The component unwraps geo.map with toRaw and passes plain objects for everything else. markRaw on imported geometry is still worth adding yourself, so Vue never proxies it in the first place:

import { markRaw } from 'vue'
import counties from './us-counties.json'

const options = { geo: { map: markRaw(counties) } }

Deep watching stops at the geometry. Configuration is watched deeply; geo.map is watched by reference alone. Replacing it with a new object (or a different registry id) is a change; rebuilding an equal topology object on every render is also seen as a change and will reproject every time, which is the one case where the markRaw constant matters for correctness rather than speed.

Inline formatters are compared by source rather than identity, so :options="{ dataLabels: { formatter: v => `${v}%` } }" written directly in a template does not redraw on every render.

Sizing

Use the width and height props, not CSS. The map's height comes from options.chart.height, which defaults to 400; an explicit number wins over the container, so a style="height: 600px" on its own gives you a 600px box with a 400px map inside it.

Width behaves differently, because chart.width defaults to '100%': it follows the container and keeps following it as the container resizes. To have the height follow the container too, say so explicitly:

<ApexMaps :options="options" height="100%" style="height: 60vh" />

The DOM it renders

Two nested <div>s. Vue owns the outer one, where your fallthrough attributes land. ApexMaps owns the inner one and writes its own class and CSS custom properties there. Vue patches class by assigning the whole attribute, so sharing one element would mean a later class change deletes the apexmaps class and every style rule in the package stops matching.

Nuxt and SSR

Safe to import and render on the server: the component emits its container and does nothing else, because everything that touches a document happens in onMounted. It hydrates and then builds the map on the client. Wrap it in <ClientOnly> if you would rather not render the empty container at all.