---
url: /migration-guides/vue-mapbox-gl.md
---
# @studiometa/vue-mapbox-gl → @studiometa/ui-mapbox
This guide helps you migrate from the Vue 3 library [`@studiometa/vue-mapbox-gl`](https://www.npmjs.com/package/@studiometa/vue-mapbox-gl) to the [js-toolkit](https://js-toolkit.studiometa.dev/) components published in [`@studiometa/ui-mapbox`](https://www.npmjs.com/package/@studiometa/ui-mapbox).
\[\[toc]]
## Why migrate
`@studiometa/ui-mapbox` re-implements the Mapbox GL components on top of [js-toolkit](https://js-toolkit.studiometa.dev/) instead of Vue. The main benefits are:
* **No Vue dependency** — the components are plain js-toolkit classes bound to the DOM through `data-component` attributes. You do not need Vue in your project to render a map.
* **Lighter for simple maps** — you ship the map logic and Mapbox GL, without a framework runtime, which keeps the footprint small for mostly-static maps.
* **Server-rendered friendly** — the map is authored as regular HTML and enhanced in place, which fits Twig/Blade/Nunjucks templates naturally.
The trade-off is that component options are **not reactive** (see [Reactivity caveat](#reactivity-caveat)). If your map relies heavily on reactive props driven by application state, weigh that difference before migrating.
## Install and setup
Replace the Vue library with the js-toolkit package and its peers.
```bash
npm remove @studiometa/vue-mapbox-gl
npm install @studiometa/ui-mapbox mapbox-gl
# Optional, only if you use the geocoder
npm install @mapbox/mapbox-gl-geocoder
```
The Mapbox GL stylesheet is still required. Keep importing it as before:
```css
@import 'mapbox-gl/dist/mapbox-gl.css';
```
Instead of registering the components on a Vue app, register them with js-toolkit. Every component is self-registering — `MapboxMap` no longer declares its children, so each one must be registered with [`registerComponent`](https://js-toolkit.studiometa.dev/api/helpers/registerComponent.html). Register only the ones you use — a bare map needs only `MapboxMap`, but every marker, control, source or cluster you declare needs its own registration. Registration order does not matter, because a child registered before its `MapboxMap` still wires up once the map connects. Because `mapbox-gl` is heavy (~230 kB gzipped), the recommended default is to lazy-register each component with js-toolkit's [`importWhen*` helpers](https://js-toolkit.studiometa.dev/api/helpers/importWhenVisible.html) and the per-component subpaths (each subpath's default export is the component class):
```js
import { registerComponent, importWhenVisible } from '@studiometa/js-toolkit';
// Register only the components your page uses; order doesn't matter.
registerComponent(importWhenVisible(() => import('@studiometa/ui-mapbox/MapboxMap'), 'MapboxMap'));
registerComponent(
importWhenVisible(() => import('@studiometa/ui-mapbox/MapboxMarker'), 'MapboxMarker'),
);
registerComponent(
importWhenVisible(() => import('@studiometa/ui-mapbox/MapboxPopup'), 'MapboxPopup'),
);
```
## Component mapping
Most Vue components have a same-named js-toolkit equivalent, which you author as `data-component` elements nested inside a `MapboxMap` instead of Vue templates. The clustering and store-locator components are the exception — they were re-architected around a new `MapboxClusterItem`, detailed below the table.
| `@studiometa/vue-mapbox-gl` | `@studiometa/ui-mapbox` | Notes |
| --------------------------- | ------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `MapboxMap` | `MapboxMap` | Root component, owns the map instance. |
| `MapboxMarker` | `MapboxMarker` | |
| `MapboxPopup` | `MapboxPopup` | Content comes from the element's inner HTML. |
| `MapboxNavigationControl` | `MapboxNavigationControl` | |
| `MapboxGeolocateControl` | `MapboxGeolocateControl` | |
| `MapboxFullscreenControl` | `MapboxFullscreenControl` | Ported. |
| `MapboxGeocoder` | `MapboxGeocoder` | Needs the optional `@mapbox/mapbox-gl-geocoder`. |
| `MapboxSource` | `MapboxSource` | Ported. |
| `MapboxLayer` | `MapboxLayer` | |
| `MapboxImage` | `MapboxImage` | Ported. |
| `MapboxImages` | `MapboxImages` | Ported. |
| `MapboxCluster` | `MapboxCluster` | Re-architected — the source is derived from child `MapboxClusterItem`s, not a `data` prop. See [below](#mapboxcluster-data). |
| — | `MapboxClusterItem` | **New** — a rendered cluster entry (list item + map feature). See [below](#mapboxcluster-data). |
| `StoreLocator` | `StoreLocator` | Re-implemented as a thin orchestrator over `MapboxMap` + `MapboxCluster` + `MapboxClusterItem`s. See its [documentation](/reference/items/StoreLocator/). |
| `VueScroller` | — | No equivalent needed — the store list is a plain scrollable element, styled with CSS. |
::: tip StoreLocator is now available
`StoreLocator` has been re-implemented as a thin **orchestrator** over a `MapboxMap` + `MapboxCluster` + `MapboxClusterItem`s (plus an optional `MapboxGeocoder`). The single-purpose `StoreLocatorItem` class of the Vue library is gone: each store is a `MapboxClusterItem` that is at once the sidebar list entry and the map feature. The `VueScroller` helper has no equivalent — the list is a plain scrollable element you style with CSS. See the [`StoreLocator` documentation](/reference/items/StoreLocator/).
:::
## API translation
### Props → options
Vue props become js-toolkit `data-option-*` attributes on the corresponding element, in kebab-case. Objects and arrays are passed as JSON strings.
```html
Capital of France
Capital of France