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:

<!-- 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.

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