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:
<!-- 2. Load MapLibre's JS + CSS BEFORE @wirekitScripts so window.maplibregl
exists when WireKit's map glue initializes. -->
<link href="https://unpkg.com/maplibre-gl/dist/maplibre-gl.css" rel="stylesheet" />
<script src="https://unpkg.com/maplibre-gl/dist/maplibre-gl.js"></script>
@wirekitScripts
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 (roughly 750 KB gzipped for the full engine). 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 ~750 KB engine ships on map routes only. --}}
@push('head')
<link href="https://unpkg.com/maplibre-gl/dist/maplibre-gl.css" rel="stylesheet" />
<script src="https://unpkg.com/maplibre-gl/dist/maplibre-gl.js"></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. Supply a dark styleUrl and switch it
when the .dark class is present:
{{-- Bind styleUrl to the active theme so the tiles match light/dark. --}}
<x-wirekit::map
:center="[52.5200, 13.4050]"
:zoom="11"
x-bind:style-url="document.documentElement.classList.contains('dark')
? 'https://tiles.example.com/dark/style.json?key=YOUR_KEY'
: 'https://tiles.example.com/light/style.json?key=YOUR_KEY'"
/>
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.
Interactive map needs a map library — the locations are listed alongside.
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.
Interactive map needs a map library — the locations are listed alongside.
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.)
Interactive map needs a map library — the locations are listed alongside.
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).
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.
Interactive map needs a map library — the locations are listed alongside.
Interactive map needs a map library — the locations are listed alongside.
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 text —
label+body: a small card with the name on top and a muted detail line (address, opening hours) below. - Text with photo — add an
imageURL: the photo renders as a card above the text, like showing the storefront at its location. - Photo only —
image+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.
Interactive map needs a map library — the locations are listed alongside.
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
- Map overview — the engine-independent API, markers, list, and Livewire wiring
- MapLibre GL JS documentation
- MapLibre styles & sources spec