Skip to main content
WireKit
Copy for LLM

MapLibre GL

MapLibre GL JS is the default engine behind <x-wirekit::map>. It renders vector tiles, so the map is fully styleable — you control the colors, fonts, and layers through a style document rather than baked-in raster images. The engine is open source under the BSD-3-Clause license and never enters WireKit's bundle; you load it as a peer dependency.

This guide covers the MapLibre engine specifically. For the marker model, the accessible list, the props, and Livewire wiring — all of which are engine-independent — see the Map overview.

Install

# 1. Install the engine (a peer dependency — not bundled by WireKit).
npm install maplibre-gl

Then load the engine in your layout — order matters, WireKit's glue looks for it at Alpine init. Serve it from your own build, which is what step 1 put in node_modules:

<!-- 2. Load MapLibre's JS + CSS BEFORE @wirekitScripts so window.maplibregl
     exists when WireKit's map glue initializes. Vite emits both from the
     `import 'maplibre-gl'` in your route entry. -->
@vite(['resources/js/maps.js'])
@wirekitScripts

If you load it from a CDN instead

A CDN is fine for a prototype, with one thing to get right: pin the version.

<!-- Replace X.Y.Z with the version in YOUR package.json — the one step 1
     installed. An unpinned https://unpkg.com/maplibre-gl/… URL is not a
     shortcut, it is a subscription: it serves whatever is newest at the moment
     of the request, so the next major release reaches your users without
     anyone deciding that it should, and it reaches them on a page you have
     not tested it against. -->
<!-- 3. And an `integrity` hash beside the pin. A pinned URL fixes WHICH version the
     CDN serves; it does not make the CDN trustworthy, and a script tag without SRI
     executes whatever comes back. unpkg publishes the hash for any file — append
     `?meta` to the URL — and the browser refuses the response when it does not
     match. `crossorigin="anonymous"` is required for SRI to be checked at all. -->
<link href="https://unpkg.com/[email protected]/dist/maplibre-gl.css" rel="stylesheet"
      integrity="sha384-REPLACE-WITH-THE-HASH-FOR-YOUR-VERSION" crossorigin="anonymous" />
<script src="https://unpkg.com/[email protected]/dist/maplibre-gl.js" defer
        integrity="sha384-REPLACE-WITH-THE-HASH-FOR-YOUR-VERSION" crossorigin="anonymous"></script>
@wirekitScripts

A pin fixes what is served; it does not verify it. Add an integrity attribute alongside it if a third party executing script in your origin is part of your threat model — and this engine is by far the largest single piece of it.

provider="maplibre" is the default, so once window.maplibregl is present the map upgrades from the marker-list fallback to a live MapLibre canvas automatically — no prop changes needed.

Bundle size

MapLibre GL is a large library — the full engine is several times the size of a raster alternative like Leaflet, and its own documentation carries the current figure. It is your app's dependency, not WireKit's — WireKit never bundles it — so where you load it is your call. Loading it globally (a <script> in every layout, or an import 'maplibre-gl' in your shared app.js) puts that weight on every page, even pages with no map. Load it only on the routes that actually render a map instead.

The simplest approach is a per-page asset stack — the layout exposes a stack, and only your map pages push the engine into it:

{{-- Layout (resources/views/layouts/app.blade.php) --}}

{{-- 1. Expose a head stack BEFORE @wirekitScripts so a page can inject the
        engine ahead of WireKit's map glue (which reads window.maplibregl at
        Alpine init). Non-map pages push nothing, so they never load it. --}}
@stack('head')
@wirekitScripts
{{-- A page that renders a map --}}

{{-- 2. Push the engine assets only from this page — they reach the layout's
        head stack, so the engine ships on map routes only rather than on every page. --}}
@push('head')
    <link href="https://unpkg.com/[email protected]/dist/maplibre-gl.css" rel="stylesheet"
      integrity="sha384-REPLACE-WITH-THE-HASH-FOR-YOUR-VERSION" crossorigin="anonymous" />
    <script src="https://unpkg.com/[email protected]/dist/maplibre-gl.js" defer
        integrity="sha384-REPLACE-WITH-THE-HASH-FOR-YOUR-VERSION" crossorigin="anonymous"></script>
@endpush

<x-wirekit::map :center="[51.5, -0.12]" :zoom="11" :markers="$stops" />

If you compile the engine through a bundler instead of a CDN, the same principle applies: import maplibre-gl in a route-specific entry (or a dynamic import('maplibre-gl') that resolves before the map mounts and assigns window.maplibregl), never in the global app entry. Until the engine is present the component shows its accessible marker list, so the page is never broken while the engine loads.

Map Styles

MapLibre reads a style document (a JSON describing sources + layers). Point styleUrl at one:

{{-- A vector style from any provider. Many require an API key in the URL. --}}
<x-wirekit::map
    :center="[52.5200, 13.4050]"
    :zoom="12"
    style-url="https://tiles.example.com/streets/style.json?key=YOUR_KEY"
    :markers="$stores"
/>

With no styleUrl, the map falls back to MapLibre's keyless demo tiles (demotiles.maplibre.org). Those are built from Natural Earth data for demonstration only — great for a country-level overview, but they carry no street-level detail. For real street maps, supply a styleUrl from a tile provider (keyless options exist; most commercial providers issue an API key).

Dark Mode

Dark mode is a different tile style, not a CSS filter over the canvas — a filter would wash out labels and markers. Pick the style on the server, from whatever your application already knows about the reader's theme, and pass it as the styleUrl prop:

{{-- $dark is your application's own theme flag — a session value, a user
     preference column, whatever decides the `.dark` class on <html>. --}}
<x-wirekit::map
    :center="[52.5200, 13.4050]"
    :zoom="11"
    :style-url="$dark
        ? 'https://tiles.example.com/dark/style.json?key=YOUR_KEY'
        : 'https://tiles.example.com/light/style.json?key=YOUR_KEY'"
/>

styleUrl is read once, when the map boots, and there is no client-side attribute behind it: the value travels inside the component's Alpine payload, which the server writes at render time. So x-bind:style-url does not switch the style — it sets an HTML attribute nothing reads, styleUrl never reaches the payload at all, and the map quietly falls back to the keyless demo tiles above.

A live theme toggle therefore has to re-create the map rather than re-style it. Under Livewire, give the tag a wire:key that carries the theme, so a change replaces the element instead of patching it and the map boots against the new style:

<x-wirekit::map
    wire:key="store-map-{{ $dark ? 'dark' : 'light' }}"
    :center="[52.5200, 13.4050]"
    :zoom="11"
    :style-url="$dark ? $darkStyleUrl : $lightStyleUrl"
/>

Store Locator

A multi-marker map is the classic store-locator surface: every location is a themed marker on the canvas and a focusable row in the list. The list is what a screen-reader user (or anyone) navigates; the map is the visual layer on top.

Store locator with regional markers

Interactive map needs a map library — the locations are listed alongside.

Stores ()

  • No locations

Color-Coded Markers

A marker's intent colors its list dot — success / warning / danger / accent — so a status map (fleet tracking, service coverage, an incident board) reads at a glance. The label always carries the status in words too, so the meaning never rides on the dot color alone.

Fleet status by color

Interactive map needs a map library — the locations are listed alongside.

Vehicles ()

  • No locations

Single Location

For a "where to find us" map, a single marker plus a tight center/zoom does the job. The label carries the full address so the location is reachable from the list without reading the map. (Supply a styleUrl for street-level tiles — the keyless demo tiles shown here stop at a regional overview.)

A single point of interest

Interactive map needs a map library — the locations are listed alongside.

Visit us ()

  • No locations

Map Only

Need just the map? Pass list="false" to drop the sidebar — the canvas fills the width and the marker list stays sr-only for assistive tech (the prop is on the Map overview).

Map only — no sidebar

Interactive map needs a map library — the locations are listed alongside.

Locations ()

  • No locations

Selection Highlight

Selecting a store — by clicking its list row or its map pin — highlights the matching row. Two styles via highlight: ring (the default — an inset frame) or fill (a soft tinted-tile background). Either way highlight-color (accent / success / warning / danger / neutral) sets the color so the selection reads in your intent color rather than the near-black stock accent. Click a row in each map below to compare.

Ring selection (default)

Interactive map needs a map library — the locations are listed alongside.

Regions ()

  • No locations
Fill selection in a chosen color

Interactive map needs a map library — the locations are listed alongside.

Regions ()

  • No locations

Pin Tooltips

Hovering (or tapping) a pin opens a bubble whose content follows the marker's data shape — four variants, no extra configuration:

  • Text only — just a label: a single bold line.
  • Styled textlabel + body: a small card with the name on top and a muted detail line (address, opening hours) below.
  • Text with photo — add an image URL: the photo renders as a card above the text, like showing the storefront at its location.
  • Photo onlyimage + tooltip: 'image': the bubble is just the photo; the label still names the pin for screen readers and stays in the marker list.

Sanitize image URLs you don't control — the adapter escapes the src.

One map, four tooltip variants

Interactive map needs a map library — the locations are listed alongside.

Stores ()

  • No locations

Tiles, Attribution & Cost

The MapLibre engine is free (BSD-3-Clause), but the tiles it renders are a separate concern with their own terms:

  • Demo tiles (demotiles.maplibre.org) — keyless, Natural Earth data, intended for demos and testing only. Not for production.
  • Commercial providers — issue an API key and bill by map load or tile request. They handle attribution requirements in their style documents.
  • Self-hosted tiles — generate your own vector tiles (e.g. from OpenStreetMap data) and serve them yourself; you own the cost and the attribution.

Whatever the source, display its required attribution visibly on the map, and read its usage policy before shipping at scale.

License

MapLibre GL JS is distributed under the BSD-3-Clause license (a permissive open-source license). WireKit treats it as an optional peer dependency — you install and load it; WireKit never bundles or relicenses it.

See Also

Was this page helpful?

Thank you for your feedback!

Voting requires cookies or local storage. What we store