Interactive maps for Mantine, powered by MapLibre GL JS. Markers with Mantine tooltips and popovers, custom Mantine-styled controls (zoom, compass, fullscreen, geolocate), and polygons, all built on Mantine's polymorphic factory and styles API.
yarn add mantine-map maplibre-gl
# or
npm install mantine-map maplibre-glImport the styles once in your app, after the Mantine core styles. mantine-map/styles.css already
includes the required maplibre-gl styles, so you do not need to import them separately:
import "@mantine/core/styles.css";
import "mantine-map/styles.css";
mantine-mapdoes not ship any map tiles. Pass a style URL from a tile provider (for example MapTiler or CARTO) via themapStyleprop. When omitted, the MapLibre demo basemap is used, which is for demonstration only.
mantine-mapis published as ES modules only, matchingmaplibre-glv6. Bundlers such as Next.js, Vite and webpack 5 need no configuration, andrequire()works on Node 22.12+ through its built-in ESM support. MapLibre GL JS v6 also requires WebGL2, which is available in all current browsers.
MapLibre GL JS v6 loads its renderer worker from a URL instead of through the bundler's module graph,
so the URL has to be supplied once before the first map is created. Without it the map mounts but
never draws any tiles, leaving an empty box. setWorkerUrl is re-exported for convenience:
// Vite. `?worker&url` is required, not plain `?url`: the worker imports a sibling chunk at runtime.
import { setWorkerUrl } from "mantine-map";
import workerUrl from "maplibre-gl/dist/maplibre-gl-worker.mjs?worker&url";
setWorkerUrl(workerUrl);// webpack 5, rspack, rsbuild
import { setWorkerUrl } from "mantine-map";
setWorkerUrl(new URL("maplibre-gl/dist/maplibre-gl-worker.mjs", import.meta.url).toString());With Next.js, copy both maplibre-gl-worker.mjs and maplibre-gl-shared.mjs out of
node_modules/maplibre-gl/dist into public/, then point setWorkerUrl at them there, prefixed with
your basePath if the app is deployed under one. The two files must stay side by side, because the
worker imports the shared chunk as a sibling and a plain new URL(...) emits only the entry file.
Turbopack can also bundle the worker properly through new Worker(new URL(...)), which resolves that
sibling import and needs no copied files. Reaching it is awkward, though: maplibre accepts only a URL
string, and Turbopack does not expose the URL of a worker entry the way Vite's ?worker&url does. This
repository's documentation site takes that route in
docs/maplibre-worker-url.ts, which spells out what it depends on. Prefer
the public/ copy above unless you specifically want to avoid the copy step.
import { Tooltip } from "@mantine/core";
import {
IconCurrentLocation,
IconMaximize,
IconMinimize,
IconMinus,
IconNavigation,
IconPlus,
} from "@tabler/icons-react";
import { Map } from "mantine-map";
function Demo() {
return (
<Map
mapStyle="https://your-tile-provider/style.json"
initialViewState={{ longitude: 2.3522, latitude: 48.8566, zoom: 11 }}
style={{ height: 460 }}
>
<Map.Controls position="top-right">
{/* Controls ship no icons, pass your own as children */}
<Map.ControlGroup>
<Map.ZoomInControl>
<IconPlus size={18} />
</Map.ZoomInControl>
<Map.ZoomOutControl>
<IconMinus size={18} />
</Map.ZoomOutControl>
</Map.ControlGroup>
<Map.CompassControl>
<IconNavigation size={18} />
</Map.CompassControl>
<Map.FullscreenControl>
{(isFullscreen) =>
isFullscreen ? <IconMinimize size={18} /> : <IconMaximize size={18} />
}
</Map.FullscreenControl>
<Map.GeolocateControl>
<IconCurrentLocation size={18} />
</Map.GeolocateControl>
</Map.Controls>
<Tooltip label="Paris">
<Map.Marker longitude={2.3522} latitude={48.8566} />
</Tooltip>
<Map.Polygon
id="zone"
coordinates={[
[2.32, 48.87],
[2.38, 48.87],
[2.38, 48.84],
[2.32, 48.84],
]}
fillColor="blue"
/>
</Map>
);
}Every subcomponent is also exported as a flat component (MapMarker, MapControls, MapZoomInControl, MapPolygon, ...) in addition to the Map.* static members.
Map— the root. Owns the MapLibre instance and shares it through context. AcceptsmapStyle(URL, style object, or a theme-aware{ light, dark }pair swapped with the Mantine color scheme),initialViewState,minZoom/maxZoom,interactive, andonMapClick/onMove/onMoveEnd/onMapLoad.onMapClickreceives the{ longitude, latitude }under the pointer, so it can be handed straight to aMap.Markerto add markers on click, and stays distinct from the root element's plainonClick.Map.Marker— renders custom content (or a default themed pin) at a coordinate. SupportsdraggableandonDragEnd. Attach a tooltip or popover by wrapping the marker with Mantine'sTooltiporPopoverdeclaratively.Map.Controls+Map.ZoomInControl,Map.ZoomOutControl,Map.CompassControl,Map.FullscreenControl,Map.GeolocateControl— MantineActionIconcontrols, positioned in any corner. The library ships no icons: pass the icon of your choice aschildrenof each control. Wrap adjacent controls inMap.ControlGroupto join them into one segmented block.Map.Polygon— highlights an area via a GeoJSON fill + outline. Colors accept Mantine color keys.
npm run storybook— start Storybooknpm run dev— start the documentation sitenpm run docgen— regenerate props documentationnpm run build— build the packagenpm test— run the full check (format, typecheck, lint, jest)
- Login with your npm account (
npm login). - Run
npm run release:patch,npm run release:minorornpm run release:majorto publish a new version.
The documentation is deployed to GitHub Pages automatically on release, based on the repository field in package/package.json. To publish it manually, run npm run docs:deploy.