# Layers Control
> the compiled dl2.LayersControl component.
---
.. llms_copy::Layers Control
.. toc::
### Overview
Demonstrates the real Python API (this is what users would write), not the JS DEMO style
used by the other showcase pages. Two-way: the UI radios/checkboxes write activeBase /
activeOverlays back to Python; Python callbacks also push those props to flip the control.
### Live demo
.. exec::docs.layers-control.example
:code: false
### Source
```python
# File: docs/layers-control/example.py
"""
Layers Control — the compiled dl2.LayersControl component.
Demonstrates the real Python API (this is what users would write), not the JS DEMO style
used by the other showcase pages. Two-way: the UI radios/checkboxes write activeBase /
activeOverlays back to Python; Python callbacks also push those props to flip the control.
"""
import dash_leaflet2 as dl2
import dash_mantine_components as dmc
from dash import Input, Output, State, callback
from dl2_locations import MINNEAPOLIS
from dl2_shared import code_panel, header, info_panel
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"
ATTR = (
'© OpenStreetMap '
'© CARTO'
)
SENSORS = {
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties": {"name": "buoy 1"},
# GeoJSON is [lon, lat] — the opposite order to Leaflet.
"geometry": {"type": "Point", "coordinates": MINNEAPOLIS.at_lonlat(2.2, 1.0)},
},
{
"type": "Feature",
"properties": {"name": "buoy 2"},
"geometry": {"type": "Point", "coordinates": MINNEAPOLIS.at_lonlat(-1.1, -2.9)},
},
{
"type": "Feature",
"properties": {"name": "buoy 3"},
"geometry": {"type": "Point", "coordinates": MINNEAPOLIS.at_lonlat(4.5, -1.0)},
},
],
}
CODE = '''dl2.Map(children=[
dl2.LayersControl(id="lc", children=[
dl2.BaseLayer(dl2.TileLayer(url=CARTO_LIGHT), name="Light", checked=True),
dl2.BaseLayer(dl2.TileLayer(url=CARTO_DARK), name="Dark"),
dl2.BaseLayer(dl2.TileLayer(url=OSM), name="OSM"),
dl2.Overlay(dl2.Polygon(...), name="Harbor zone", checked=True),
dl2.Overlay(dl2.Circle(...), name="Buoy radius"),
dl2.Overlay(dl2.GeoJSON(data=SENSORS), name="Sensors"),
]),
])
# activeBase / activeOverlays are TWO-WAY:
@callback(Output("out", "children"),
Input("lc", "activeBase"), Input("lc", "activeOverlays"))
def show(b, o):
return f"{b} | {o}"
@callback(Output("lc", "activeBase"), Input("dark-btn", "n_clicks"),
prevent_initial_call=True)
def force_dark(_):
return "Dark"'''
component = dmc.Stack(
[
header(
"Layers Control",
"dl2.LayersControl renders a Leaflet control with N base layers (radio) and M "
"overlays (checkbox). BaseLayer/Overlay capture their child layer via a React-context "
"map proxy and register it with the control. activeBase + activeOverlays are two-way.",
badge="dl2.LayersControl",
),
dmc.Grid(
[
dmc.GridCol(
dmc.Paper(
dl2.Map(
id="lc-map",
center=MINNEAPOLIS.center,
zoom=12,
style={"height": "60vh"},
children=[
dl2.LayersControl(
id="lc",
position="topright",
children=[
dl2.BaseLayer(
dl2.TileLayer(
url=CARTO_LIGHT, attribution=ATTR
),
name="Light",
checked=True,
),
dl2.BaseLayer(
dl2.TileLayer(
url=CARTO_DARK, attribution=ATTR
),
name="Dark",
),
dl2.BaseLayer(
dl2.TileLayer(url=OSM), name="OSM"
),
dl2.Overlay(
dl2.Polygon(
# (north_km, east_km) offsets
positions=MINNEAPOLIS.ring([
(3.3, -4.9),
(4.5, 2.9),
(-1.1, 4.9),
(-2.2, -2.9),
]),
color="#2f9e44",
fillOpacity=0.25,
children=dl2.Tooltip(
children="harbor zone"
),
),
name="Harbor zone",
checked=True,
),
dl2.Overlay(
dl2.Circle(
center=MINNEAPOLIS.at(-2.2, 1.0),
radius=1500,
color="#e8590c",
fillOpacity=0.2,
children=dl2.Tooltip(
children="buoy radius"
),
),
name="Buoy radius",
),
dl2.Overlay(
dl2.GeoJSON(
data=SENSORS,
style={"color": "#9c36b5", "weight": 2},
),
name="Sensors",
),
],
),
],
),
shadow="sm",
radius="md",
withBorder=True,
style={"overflow": "hidden"},
),
span=8,
),
dmc.GridCol(
dmc.Stack(
[
info_panel(
"Active state (map → Python)",
dmc.Code(
id="lc-out",
block=True,
style={
"minHeight": "84px",
"whiteSpace": "pre-wrap",
},
),
),
info_panel(
"Python → control",
dmc.Stack(
[
dmc.Text(
"Pick a base from a Python callback:",
size="sm",
c="dimmed",
),
dmc.Group(
[
dmc.Button(
"Light",
id="lc-btn-light",
size="xs",
variant="light",
color="gray",
),
dmc.Button(
"Dark",
id="lc-btn-dark",
size="xs",
variant="light",
color="dark",
),
dmc.Button(
"OSM",
id="lc-btn-osm",
size="xs",
variant="light",
color="blue",
),
]
),
dmc.Text(
"Toggle an overlay from Python:",
size="sm",
c="dimmed",
mt="sm",
),
dmc.Group(
[
dmc.Button(
"Toggle Buoy radius",
id="lc-btn-buoy",
size="xs",
variant="light",
color="orange",
),
dmc.Button(
"Toggle Sensors",
id="lc-btn-sensors",
size="xs",
variant="light",
color="grape",
),
]
),
],
gap="xs",
),
),
],
gap="md",
),
span=4,
),
]
),
code_panel("dl2.LayersControl pattern", CODE),
],
gap="md",
)
# Two-way: map -> Python (state readout) ----------------------------------------
@callback(
Output("lc-out", "children"),
Input("lc", "activeBase"),
Input("lc", "activeOverlays"),
)
def show_active(base, overlays):
return f"activeBase:\n {base}\nactiveOverlays:\n {overlays}"
# Two-way: Python -> map (button-driven base switch) ----------------------------
@callback(
Output("lc", "activeBase", allow_duplicate=True),
Input("lc-btn-light", "n_clicks"),
prevent_initial_call=True,
)
def to_light(_):
return "Light"
@callback(
Output("lc", "activeBase", allow_duplicate=True),
Input("lc-btn-dark", "n_clicks"),
prevent_initial_call=True,
)
def to_dark(_):
return "Dark"
@callback(
Output("lc", "activeBase", allow_duplicate=True),
Input("lc-btn-osm", "n_clicks"),
prevent_initial_call=True,
)
def to_osm(_):
return "OSM"
def _toggle(current, name):
current = list(current or [])
return [o for o in current if o != name] if name in current else current + [name]
@callback(
Output("lc", "activeOverlays", allow_duplicate=True),
Input("lc-btn-buoy", "n_clicks"),
State("lc", "activeOverlays"),
prevent_initial_call=True,
)
def toggle_buoy(_, current):
return _toggle(current, "Buoy radius")
@callback(
Output("lc", "activeOverlays", allow_duplicate=True),
Input("lc-btn-sensors", "n_clicks"),
State("lc", "activeOverlays"),
prevent_initial_call=True,
)
def toggle_sensors(_, current):
return _toggle(current, "Sensors")
```
:defaultExpanded: false
:withExpandedButton: true
---
*Source: /layers-control*