**Site index:** [https://2plot.dev/llms.txt](https://2plot.dev/llms.txt) — every page on this site, as Markdown.  
**Network index:** [https://2plot.ai/llms.txt](https://2plot.ai/llms.txt) — The 2plot network; start here to discover sibling sites.  
**Sibling sites:** 13 more in The 2plot network — listed in the site index above.  
**Sitemap:** https://2plot.dev/sitemap.xml  

> **Full documentation:** [https://leaflet.2plot.dev](https://leaflet.2plot.dev) — the dedicated dash-leaflet2 documentation site, with the complete API reference and deeper examples. This page is the quick-start overview.



### Installation

[Visit GitHub Repo](https://github.com/pip-install-python/dash-leaflet2)

```bash
pip install dash-leaflet2
```

### Introduction

`dash-leaflet2` is a **Leaflet 2-native rewrite** of Dash mapping components. The original `dash-leaflet` is frozen on react-leaflet, which is built around Leaflet 1.9's context/lifecycle model and has no Leaflet 2 line. `dash-leaflet2` wraps **Leaflet 2 core directly** (no react-leaflet) via a tiny React-context bridge, targeting Dash 4 and working toward dash-leaflet 1.x feature parity.

Shedding the react-leaflet abstraction layer unlocks Leaflet 2's headline features:

*   **Unified Pointer Events** — one event model for mouse, touch, and pen.
*   **`ResizeObserver`-based sizing** — no more gray tiles when a map lives inside tabs or accordions.
*   **ES6-class subclassing** and canvas/WebGL `BlanketOverlay` layers.
*   **Bundled everything** — Leaflet 2, marker images, iconify-icon, and a liquid-glass theme ship inside `dash_leaflet2.js`. No CDN, no JS build step at install time.

Version `0.1.0` ships **26 components**: `Map`, `TileLayer`, `Marker`, `Popup`, `Tooltip`, `LayersControl`, `BaseLayer`, `Overlay`, `GeoJSON` (with SuperCluster clustering), `EditControl`, `FeatureGroup`, `LayerGroup`, `Circle`, `CircleMarker`, `Polygon`, `Polyline`, `Rectangle`, `ImageOverlay`, `MiniMap`, `ScaleControl`, `FullScreenControl`, `AttributionControl`, `KeyboardControl`, `TextMarker`, `TileSelector`, and `EasyButton`.

```python
import dash_leaflet2 as dl2
```

> Status: **alpha**, tracking Leaflet `2.0.0-alpha.1`. APIs may change.

---

### Quick Start

The core trio — `dl2.Map` + `dl2.TileLayer` + `dl2.Marker` — with a rich `dl2.Popup` (any Dash component works as popup content). Markers support four icon modes: the default pin, a custom image (`icon`), any `emoji`, or 200k+ Iconify icons (`iconify`).

**Important:** always give the Map an explicit height via `style`, e.g. `style={"height": "500px", "width": "100%"}`.



```python
# File: docs/dash_leaflet2/quick_start.py

import dash_leaflet2 as dl2
import dash_mantine_components as dmc
from dash import html

CENTER = [28.0206, -97.0544]

component = dmc.Paper(
    withBorder=True,
    p="md",
    children=[
        dmc.Stack(
            [
                dmc.Text("Map + TileLayer + Marker + Popup", fw=500),
                dmc.Text(
                    "The core trio: a Map with an OpenStreetMap TileLayer and a draggable "
                    "Marker hosting a rich Popup. Drag the marker — its position round-trips "
                    "back to Dash.",
                    size="sm",
                    c="dimmed",
                ),
                dl2.Map(
                    id="leaflet2-quickstart-map",
                    center=CENTER,
                    zoom=12,
                    style={"height": "500px", "width": "100%", "borderRadius": "8px", "overflow": "hidden"},
                    children=[
                        dl2.TileLayer(),
                        dl2.Marker(
                            id="leaflet2-quickstart-marker",
                            position=CENTER,
                            draggable=True,
                            children=[
                                dl2.Tooltip(children="Drag me!"),
                                dl2.Popup(
                                    children=html.Div(
                                        [
                                            html.B("dl2.Marker + dl2.Popup"),
                                            html.Br(),
                                            "Any Dash component works as popup content — "
                                            "it renders through a React portal.",
                                        ]
                                    ),
                                    maxWidth=260,
                                ),
                            ],
                        ),
                        dl2.Marker(
                            id="leaflet2-quickstart-emoji",
                            position=[28.045, -97.02],
                            emoji="🛥️",
                            iconSize=34,
                            popup="Emoji markers work out of the box.",
                        ),
                        dl2.Marker(
                            id="leaflet2-quickstart-iconify",
                            position=[28.0, -97.09],
                            iconify="mdi:lighthouse-on",
                            iconColor="orange",
                            iconSize=36,
                            popup="200k+ Iconify icons, lazy-loaded from the Iconify API.",
                        ),
                    ],
                ),
            ],
            gap="sm",
        )
    ],
)
```


---

### Viewport & Interaction Callbacks

The map writes state back to Dash through read-only props:

*   **`viewport`** — updated on every `moveend`/`zoomend` as `{center: [lat, lng], zoom, bearing, bounds: {north, south, east, west}}`.
*   **`clickData`** — the most recent map click as `{latlng: [lat, lng]}`.
*   **`n_movestart` / `n_moveend`** — counters for pan/fly transitions (drive a "flying…" indicator).

Draggable markers write their new `position` back too, and the `flyTo` prop goes the other way — trigger smooth `flyTo` / `setView` / `panTo` / `fitBounds` transitions from Python callbacks.



```python
# File: docs/dash_leaflet2/interactions.py

import dash_leaflet2 as dl2
import dash_mantine_components as dmc
from dash import callback, Input, Output

CENTER = [28.0206, -97.0544]

component = dmc.Paper(
    withBorder=True,
    p="md",
    children=[
        dmc.Stack(
            [
                dmc.Text("Viewport + click events in Python callbacks", fw=500),
                dmc.Text(
                    "The Map writes its view state back to Dash on every moveend/zoomend "
                    "via the read-only `viewport` prop, and the last click via `clickData`. "
                    "Pan, zoom, and click the map.",
                    size="sm",
                    c="dimmed",
                ),
                dmc.Grid(
                    [
                        dmc.GridCol(
                            dl2.Map(
                                id="leaflet2-interactions-map",
                                center=CENTER,
                                zoom=11,
                                style={"height": "500px", "width": "100%", "borderRadius": "8px", "overflow": "hidden"},
                                children=[
                                    dl2.TileLayer(),
                                    dl2.Marker(
                                        id="leaflet2-interactions-marker",
                                        position=CENTER,
                                        draggable=True,
                                        tooltip="Drag me — position echoes below",
                                    ),
                                ],
                            ),
                            span={"base": 12, "md": 8},
                        ),
                        dmc.GridCol(
                            dmc.Stack(
                                [
                                    dmc.Text("Viewport (moveend / zoomend)", size="sm", fw=500),
                                    dmc.Code(
                                        "pan or zoom the map…",
                                        id="leaflet2-interactions-viewport",
                                        block=True,
                                        style={"minHeight": "130px"},
                                    ),
                                    dmc.Text("Last map click", size="sm", fw=500),
                                    dmc.Code("click the map…", id="leaflet2-interactions-click", block=True),
                                    dmc.Text("Marker position", size="sm", fw=500),
                                    dmc.Code("drag the marker…", id="leaflet2-interactions-position", block=True),
                                ],
                                gap="xs",
                            ),
                            span={"base": 12, "md": 4},
                        ),
                    ]
                ),
            ],
            gap="sm",
        )
    ],
)


@callback(
    Output("leaflet2-interactions-viewport", "children"),
    Input("leaflet2-interactions-map", "viewport"),
    prevent_initial_call=True,
)
def leaflet2_show_viewport(vp):
    if not vp:
        return "pan or zoom the map…"
    b = vp["bounds"]
    return (
        f"center: {vp['center'][0]:.4f}, {vp['center'][1]:.4f}\n"
        f"zoom:   {vp['zoom']}\n"
        f"bounds: N {b['north']:.3f}  S {b['south']:.3f}\n"
        f"        E {b['east']:.3f}  W {b['west']:.3f}"
    )


@callback(
    Output("leaflet2-interactions-click", "children"),
    Input("leaflet2-interactions-map", "clickData"),
    prevent_initial_call=True,
)
def leaflet2_show_click(click_data):
    if not click_data:
        return "click the map…"
    lat, lng = click_data["latlng"]
    return f"{lat:.5f}, {lng:.5f}"


@callback(
    Output("leaflet2-interactions-position", "children"),
    Input("leaflet2-interactions-marker", "position"),
    prevent_initial_call=True,
)
def leaflet2_show_position(position):
    if not position:
        return "drag the marker…"
    return f"{position[0]:.5f}, {position[1]:.5f}"
```


---

### LayersControl & GeoJSON

`dl2.LayersControl` renders Leaflet's layer picker: `dl2.BaseLayer` children register as mutually-exclusive base maps (radio), `dl2.Overlay` children as independent toggles (checkbox). Both `activeBase` and `activeOverlays` are **two-way** — they reflect user choices in callbacks and accept updates from Python.

`dl2.GeoJSON` renders a FeatureCollection from the `data` prop, reports the clicked feature's `properties` via `clickFeature`, and supports SuperCluster point clustering with `cluster=True` plus `pointToLayer` / `clusterToLayer` JS hooks and a `hideout` passthrough for styling without Python round-trips.



```python
# File: docs/dash_leaflet2/layers_geojson.py

import dash_leaflet2 as dl2
import dash_mantine_components as dmc
from dash import callback, Input, Output

CENTER = [28.02, -97.05]

OSM = "https://tile.openstreetmap.org/{z}/{x}/{y}.png"
CARTO_LIGHT = "https://basemaps.cartocdn.com/light_all/{z}/{x}/{y}.png"
CARTO_DARK = "https://basemaps.cartocdn.com/dark_all/{z}/{x}/{y}.png"
CARTO_ATTR = (
    '&copy; <a href="https://openstreetmap.org/copyright">OpenStreetMap</a> '
    '&copy; <a href="https://carto.com/attributions">CARTO</a>'
)

SENSORS = {
    "type": "FeatureCollection",
    "features": [
        {
            "type": "Feature",
            "properties": {"name": "Buoy 1"},
            "geometry": {"type": "Point", "coordinates": [-97.04, 28.04]},
        },
        {
            "type": "Feature",
            "properties": {"name": "Buoy 2"},
            "geometry": {"type": "Point", "coordinates": [-97.08, 28.01]},
        },
        {
            "type": "Feature",
            "properties": {"name": "Buoy 3"},
            "geometry": {"type": "Point", "coordinates": [-97.06, 28.06]},
        },
    ],
}

component = dmc.Paper(
    withBorder=True,
    p="md",
    children=[
        dmc.Stack(
            [
                dmc.Text("LayersControl + GeoJSON", fw=500),
                dmc.Text(
                    "Base layers are mutually exclusive radios; overlays are independent "
                    "checkboxes. activeBase and activeOverlays are two-way props — the control "
                    "reports user choices to Python and accepts them from callbacks. Click a "
                    "GeoJSON point to read its feature properties.",
                    size="sm",
                    c="dimmed",
                ),
                dl2.Map(
                    id="leaflet2-layers-map",
                    center=CENTER,
                    zoom=12,
                    style={"height": "500px", "width": "100%", "borderRadius": "8px", "overflow": "hidden"},
                    children=[
                        dl2.LayersControl(
                            id="leaflet2-layers-control",
                            position="topright",
                            children=[
                                dl2.BaseLayer(
                                    dl2.TileLayer(url=CARTO_LIGHT, attribution=CARTO_ATTR),
                                    name="Light",
                                    checked=True,
                                ),
                                dl2.BaseLayer(
                                    dl2.TileLayer(url=CARTO_DARK, attribution=CARTO_ATTR),
                                    name="Dark",
                                ),
                                dl2.BaseLayer(
                                    dl2.TileLayer(url=OSM),
                                    name="OSM",
                                ),
                                dl2.Overlay(
                                    dl2.Polygon(
                                        positions=[
                                            [28.05, -97.10],
                                            [28.06, -97.02],
                                            [28.01, -97.00],
                                            [28.00, -97.08],
                                        ],
                                        color="teal",
                                        fillOpacity=0.2,
                                        children=dl2.Tooltip(children="Harbor zone"),
                                    ),
                                    name="Harbor zone",
                                    checked=True,
                                ),
                                dl2.Overlay(
                                    dl2.GeoJSON(
                                        id="leaflet2-layers-geojson",
                                        data=SENSORS,
                                    ),
                                    name="Sensors",
                                    checked=True,
                                ),
                            ],
                        ),
                    ],
                ),
                dmc.Group(
                    [
                        dmc.Text("Active layers:", size="sm", fw=500),
                        dmc.Code("—", id="leaflet2-layers-active"),
                        dmc.Text("Clicked feature:", size="sm", fw=500),
                        dmc.Code("click a sensor point…", id="leaflet2-layers-feature"),
                    ],
                    gap="xs",
                ),
            ],
            gap="sm",
        )
    ],
)


@callback(
    Output("leaflet2-layers-active", "children"),
    Input("leaflet2-layers-control", "activeBase"),
    Input("leaflet2-layers-control", "activeOverlays"),
)
def leaflet2_show_active_layers(base, overlays):
    return f"base: {base} | overlays: {overlays}"


@callback(
    Output("leaflet2-layers-feature", "children"),
    Input("leaflet2-layers-geojson", "clickFeature"),
    prevent_initial_call=True,
)
def leaflet2_show_clicked_feature(props):
    if not props:
        return "click a sensor point…"
    return str(props)
```


---

### Component Properties

Props tagged `[MUTABLE]` accept updates from callbacks (Python → map); `[READONLY]` props are written back by the map (map → Python). Full prop lists live in each generated class's docstring — `help(dl2.Map)`.

#### Map Props

| Property | Type | Default | Description |
|:---------|:-----|:--------|:------------|
| `id` | string | - | Component ID for Dash callbacks. |
| `children` | node | - | Child layers (TileLayer, Marker, ...) rendered into this map. |
| `center` | [number, number] | `[51.505, -0.09]` | Initial map center as `[lat, lng]`. [MUTABLE] |
| `zoom` | number | `13` | Initial zoom level. [MUTABLE] |
| `viewport` | dict | - | Current view state, written back on every moveend/zoomend: `{center, zoom, bearing, bounds: {north, south, east, west}}`. [READONLY] |
| `clickData` | dict | - | Most recent map click: `{latlng: [lat, lng]}`. [READONLY] |
| `flyTo` | dict | - | Python → map viewport transition: `{transition: 'flyTo'\|'setView'\|'panTo'\|'fitBounds'\|'flyToBounds'\|'panInsideBounds', center?, zoom?, bounds?, options?, n_clicks}`. [MUTABLE] |
| `maxBounds` | [[number, number], [number, number]] | - | Geographic bounds the view is constrained inside, as `[[south, west], [north, east]]`. [MUTABLE] |
| `minZoom` | number | - | Minimum zoom the user can zoom out to (most restrictive of Map/TileLayer wins). [MUTABLE] |
| `maxZoom` | number | - | Maximum zoom the user can zoom in to (smallest of Map/TileLayer wins). [MUTABLE] |
| `bearing` | number | - | Map rotation in degrees (CSS-based; keep 0 when drawing interactively). [MUTABLE] |
| `dragging` | boolean | `True` | Mouse / pointer drag panning. [MUTABLE] |
| `scrollWheelZoom` | boolean | `True` | Mouse-wheel zoom. [MUTABLE] |
| `doubleClickZoom` | boolean | `True` | Double-click-to-zoom. [MUTABLE] |
| `boxZoom` | boolean | `True` | Shift-drag box-zoom selection. [MUTABLE] |
| `pinchZoom` | boolean | `True` | Pinch-to-zoom on touch devices (v1's `touchZoom`). [MUTABLE] |
| `keyboard` | boolean | `True` | Pan/zoom with arrow keys and `+`/`-`. [MUTABLE] |
| `tapHold` | boolean | - | Mobile-Safari tap-hold-to-contextmenu emulation. [MUTABLE] |
| `n_movestart` | number | - | Counter bumped on every `movestart`. [READONLY] |
| `n_moveend` | number | - | Counter bumped on every `moveend`. [READONLY] |
| `preferCanvas` | boolean | `False` | Render all vector layers through the Canvas renderer. |
| `zoomControl` | boolean | `True` | Built-in +/- zoom buttons (constructor-only). |
| `attributionControl` | boolean | `True` | Built-in attribution control; set `False` to mount your own `dl2.AttributionControl`. Constructor-only. |
| `className` / `style` | string / object | - | CSS class / inline styles. **Set the map height via `style`.** |

#### TileLayer Props

| Property | Type | Default | Description |
|:---------|:-----|:--------|:------------|
| `id` | string | - | Component ID for Dash callbacks. |
| `url` | string | OSM tiles | Tile URL template, e.g. `https://tile.openstreetmap.org/{z}/{x}/{y}.png`. Updating it swaps the basemap. [MUTABLE] |
| `attribution` | string | `© OpenStreetMap contributors` | Attribution HTML shown on the map. |
| `opacity` | number | `1` | Layer opacity, 0..1. [MUTABLE] |
| `minZoom` | number | `0` | Minimum zoom at which this layer is visible — below it Leaflet stops requesting tiles entirely. |
| `maxZoom` | number | `19` | Maximum zoom level for this tile layer. |
| `maxNativeZoom` | number | - | Max zoom the tile source actually has tiles for; Leaflet upscales past it instead of 404-ing. |
| `bounds` | [[number, number], [number, number]] | - | Bounds outside of which no tiles are requested, as `[[south, west], [north, east]]`. |
| `errorTileUrl` | string | - | Image shown in place of a tile that fails to load (a 1x1 transparent PNG data URL hides broken tiles). |
| `zIndex` | number | - | Explicit z-index for the layer's DOM pane when stacking tile layers. [MUTABLE] |
| `subdomains` | string or list | - | Subdomains substituted into the `{s}` placeholder — `['a','b','c']` or `'abc'`. |
| `detectRetina` | boolean | `False` | Request 2x-resolution tiles on hi-DPI displays. |
| `tms` | boolean | `False` | Invert Y coordinates for TMS-shaped tile pyramids. |
| `crossOrigin` | string | - | `crossOrigin` attribute on tile img elements; pass `"anonymous"` for canvas-readable tiles. Construction-time only. |
| `className` | string | - | CSS class name(s). |

> **New in 0.1.0 (pro-parity props):** `TileLayer` gained `minZoom`, `bounds`, `errorTileUrl`, `zIndex`, `subdomains`, `detectRetina`, and `tms`; `Map` gained `maxBounds` plus the full set of interaction handler toggles (`dragging`, `scrollWheelZoom`, `doubleClickZoom`, `boxZoom`, `pinchZoom`, `keyboard`, `tapHold`).

---

### Beyond the Basics

The library also ships `dl2.EditControl` (native Leaflet 2 draw/edit with GeoJSON round-trip — no leaflet-draw), `dl2.MiniMap`, `dl2.ScaleControl`, `dl2.FullScreenControl`, `dl2.TextMarker`, `dl2.EasyButton` (Iconify-icon buttons with `n_clicks`), and `dl2.TileSelector` (hover-highlight, click-to-toggle tile selection). See the [GitHub repository](https://github.com/pip-install-python/dash-leaflet2) for the full showcase.
