---
title: Map
description: Map adapter (MapLibre/Leaflet peer dependency) with markers and an accessible location list
visibility: guest
draft: false
---

# Map

An interactive map for geo surfaces — fleet tracking, real-estate search,
field-service dispatch, store locators. Like [`chart`](/components/chart) and the
rich-text [`editor`](/components/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.

::: info
`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](#choosing-a-map-engine)
below, then the [MapLibre GL setup guide](/components/map/maplibre). 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](/components/map/maplibre#bundle-size) shows the route-scoped loading pattern.

Each engine has a dedicated guide: the **[MapLibre GL guide](/components/map/maplibre)**
(install, styling, dark-mode tiles, real-world patterns) and the
**[Leaflet & OpenStreetMap guide](/components/map/leaflet)** (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](/components/map/leaflet)
covers both layers on one page: the engine setup first, then that tile source's
attribution requirement, usage policy, and license.

::: warning
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](/components/map/maplibre)
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:

```blade
{{-- 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)"
/>
```

```php
// 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();
}
```

```javascript
// 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

::: tip
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 an `aria-label` and 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`:

```php
'components' => [
    'map' => [
        'provider' => 'leaflet',
        'zoom' => 11,
    ],
],
```

## Further Reading

- [MapLibre GL setup guide](/components/map/maplibre) — install, styling, dark mode, and real-world patterns
- [Leaflet setup guide](/components/map/leaflet) — install, raster tiles, attribution, store locator
- [Leaflet & OpenStreetMap](/components/map/leaflet) — the raster engine + the free tile source: attribution, usage policy, licenses
- [WAI: Accessible maps](https://www.w3.org/WAI/RD/wiki/Accessible_maps)
