Map
An interactive map for geo surfaces — fleet tracking, real-estate search,
field-service dispatch, store locators. Like chart and the
rich-text editor, map is an adapter: WireKit ships
the themed chrome, the declarative API, the Alpine glue, and — most importantly —
an accessible marker list that is always present. The heavy map engine and the
map tiles are your app's concern, so the engine (~200 KB) never enters WireKit's
bundle.
map needs a peer dependency — a map engine loaded on the page. Pick one:
MapLibre GL (the default — vector tiles, fully themeable) or Leaflet
(lightweight raster tiles). See Choosing a Map Engine
below, then the MapLibre GL setup guide. Without an
engine the component degrades to the marker list plus a placeholder, so your data
stays reachable.
Markers
Pass a center, a zoom, and an array of markers — each marker appears in the
accessible list alongside the map. Selecting one (from the list or by clicking
its pin on the map) pans the map, highlights the matching list entry, and emits
marker-click. A marker's intent colors both its list dot and — once a map
engine is loaded — the map pin itself, while the label and coordinates always
carry the meaning, so the map never depends on color alone. An optional body
line (an address, opening hours) renders under the label in the list, as the
second line of the pin's tooltip, and inside the row's accessible name.
Both engines render standard on-canvas zoom controls (zoom in / out), so a reader can scale the map without a scroll-wheel gesture.
This overview stays engine-agnostic; the live previews live on the engine guides below, where a real map (with real tiles) renders. Pick an engine, then follow its guide.
Choosing a Map Engine
The same <x-wirekit::map> API drives either engine — you choose with the
provider prop and supply the engine's library on the page. The marker list, the
accessible API, and the theming are identical; only the tiles and the rendering
differ.
| Engine | provider |
Tiles | License | Best for |
|---|---|---|---|---|
| MapLibre GL | maplibre (default) |
Vector, fully styleable | BSD-3-Clause | Custom-styled, themeable interactive maps |
| Leaflet | leaflet |
Raster (you pick the tile source) | BSD-2-Clause | A small footprint and any raster tile provider |
Either engine is your app's dependency, not WireKit's — so load it only on the routes that render a map, never globally, or it weighs down every page. MapLibre GL in particular is large (~750 KB gzipped); the MapLibre GL guide shows the route-scoped loading pattern.
Each engine has a dedicated guide: the MapLibre GL guide (install, styling, dark-mode tiles, real-world patterns) and the Leaflet & OpenStreetMap guide (install, raster tiles, the free OSM tile source with its attribution requirement, usage policy, and license — engine and tile source together on one page).
Engine vs. tile source — which page do I read? They are different layers, and
a live map needs both. The engine is the JavaScript library that draws the
map (MapLibre or Leaflet — that's the provider). The tile source is where
the actual map imagery comes from (OpenStreetMap, a commercial vector style, your
own server — that's the styleUrl). So Leaflet and OpenStreetMap are not
alternatives to each other: Leaflet is one engine that can render OpenStreetMap
tiles — which is why the Leaflet & OpenStreetMap guide
covers both layers on one page: the engine setup first, then that tile source's
attribution requirement, usage policy, and license.
Whichever engine you choose, the tiles carry their own license and attribution terms — they are separate from the engine's own (BSD) license. Always display the tile provider's required attribution, and check its usage policy before shipping at scale. The MapLibre GL guide covers this for each tile source.
Livewire & Realtime
Handle marker clicks server-side, and stream live positions in from Laravel
Echo by calling the component's upsertMarker method:
{{-- 1. Open a detail panel when a marker is clicked --}}
<x-wirekit::map
:center="$center"
:markers="$this->vehicles"
x-on:marker-click="$wire.selectVehicle($event.detail.id)"
/>
// 2. Shape markers for the map
public function getVehiclesProperty(): array
{
return Vehicle::all()->map(fn ($v) => [
'id' => $v->id,
'lat' => $v->latitude,
'lng' => $v->longitude,
'label' => "{$v->name} — {$v->status}",
'intent' => match ($v->status) {
'active' => 'success', 'delayed' => 'warning', default => 'danger',
},
])->all();
}
// 3. Push live position updates (optimistic; the list + map both update)
Echo.private('fleet').listen('VehicleMoved', (e) => {
window.dispatchEvent(new CustomEvent('vehicle-moved', { detail: e }));
});
// Wire it on the element: x-on:vehicle-moved.window="upsertMarker($event.detail)"
Props
| Prop | Type | Default | Description |
|---|---|---|---|
center |
array | [0, 0] |
[lat, lng] initial center |
zoom |
int | 2 |
Initial zoom level |
markers |
array | [] |
[{id, lat, lng, label, body?, image?, tooltip?, intent?}] — the pin bubble follows the data shape: label alone is a text-only line, body adds a muted detail row (address, opening hours; also shown in the list), image adds a photo card above the text, and tooltip: 'image' makes the bubble photo-only (the label still names the pin for screen readers and the list) |
provider |
string | 'maplibre' |
maplibre · leaflet (peer dependency) |
styleUrl |
string|null | null |
Tile / style URL (provider-specific) |
attribution |
string|null | null |
Tile attribution shown in Leaflet's attribution control (e.g. © OpenStreetMap contributors) — required by some tile sources |
height |
string | '24rem' |
Map canvas height (CSS length) |
ariaLabel |
string | 'Map' |
Accessible name for the map region |
listLabel |
string | 'Locations' |
Heading for the marker list |
highlight |
string | 'ring' |
Selected-row highlight: ring (inset frame) · fill (tinted tile background) |
highlightColor |
string | 'accent' |
Highlight color: accent · success · warning · danger · neutral |
list |
bool | true |
Show the marker-list sidebar; false renders a map-only surface (the list stays sr-only for accessibility) |
scope |
string|null | null |
Scoped personalization name |
Accessibility
The marker list is the screen-reader experience — always keep it visible (or at least in the DOM). The visual map is the enhancement on top.
- A map is opaque to assistive tech, so the marker list is mandatory and always rendered. Each entry is a focusable button labeled with the marker name and its coordinates, so the data is reachable without seeing the map.
- The map canvas carries
role="application"with anaria-labeland is keyboard-focusable; the list region is a labeled, keyboard-reachable scroll region (WCAG 2.1.1). - Marker status is conveyed by the label text, never the dot color alone.
- Pan/fly animations honor
prefers-reduced-motion(the map jumps instead).
Keyboard Interaction
| Key | Action |
|---|---|
Tab |
Move into the map canvas and through the marker list |
Enter / Space |
Select a marker (pans the map, emits marker-click) |
Design Tokens
| Element | Token |
|---|---|
| Canvas backdrop | --color-wk-bg-muted |
| List surface | --color-wk-bg-elevated |
| Marker dot (accent/success/warning/danger) | --color-wk-accent / --color-wk-success / --color-wk-warning / --color-wk-danger |
| Borders | --color-wk-border |
| Focus ring | --color-wk-ring |
Customization
Set the default provider and zoom via config/wirekit.php:
'components' => [
'map' => [
'provider' => 'leaflet',
'zoom' => 11,
],
],
Further Reading
- MapLibre GL setup guide — install, styling, dark mode, and real-world patterns
- Leaflet setup guide — install, raster tiles, attribution, store locator
- Leaflet & OpenStreetMap — the raster engine + the free tile source: attribution, usage policy, licenses
- WAI: Accessible maps