Leaflet.js Custom Controls — Building UI for Map Interactions
In this tutorial, you will learn about Leaflet.js Custom Controls. We cover key concepts, practical examples, and best practices to help you master this topic.
Leaflet.js custom controls allow you to build and position custom UI elements on the map using L.Control with custom HTML, events, and positioning.
What You'll Learn
By the end of this guide, you will create custom controls with HTML content, position controls in any corner, handle control button click events, build a custom legend control, and create a toolbar with multiple buttons.
Basic Custom Control
var InfoControl = L.Control.extend({
options: {
position: 'topright'
},
onAdd: function(map) {
var container = L.DomUtil.create('div', 'info-control leaflet-bar');
container.innerHTML = '<button id="info-btn">Info</button>';
container.style.backgroundColor = 'white';
container.style.padding = '8px 12px';
container.style.borderRadius = '4px';
container.style.cursor = 'pointer';
return container;
}
});
map.addControl(new InfoControl());
Control with Events
var DrawControl = L.Control.extend({
options: { position: 'topleft' },
onAdd: function(map) {
var container = L.DomUtil.create('div', 'draw-control leaflet-bar');
container.innerHTML = '<button id="draw-btn" class="leaflet-control-button">Draw</button>';
L.DomEvent.on(container, 'click', function() {
map.fire('custom:draw');
});
L.DomEvent.disableClickPropagation(container);
return container;
}
});
Legend Control
var LegendControl = L.Control.extend({
options: { position: 'bottomright' },
onAdd: function() {
var div = L.DomUtil.create('div', 'legend-control');
div.innerHTML = '<h4>Legend</h4>' +
'<div><span style="background:#4ecdc4;width:12px;height:12px;display:inline-block;margin-right:4px;"></span>Parks</div>' +
'<div><span style="background:#ff6b35;width:12px;height:12px;display:inline-block;margin-right:4px;"></span>Buildings</div>';
return div;
}
});
Common Mistakes
1. Not Using L.DomEvent.disableClickPropagation
Clicks on controls propagate to the map. Always disable click propagation to prevent unwanted map interactions.
2. Missing CSS Styling
Custom controls need explicit CSS for background, border, and padding. Otherwise they inherit map styles.
3. Positioning Conflicts
Two controls at the same position overlap. Use different positions or stack them manually.
4. Not Returning Container from onAdd
onAdd must return a DOM element. Returning undefined causes errors.
5. Forgetting L.DomUtil.create
Use L.DomUtil.create for cross-browser element creation. Avoid innerHTML for dynamic content with user input.
Practice Questions
Q1: What method must every control implement? A: onAdd, which returns a DOM element to display on the map.
Q2: How do you set control position? A: Set the position option: 'topleft', 'topright', 'bottomleft', 'bottomright'.
Q3: How do you prevent click events from reaching the map? A: Call L.DomEvent.disableClickPropagation on the container element.
Q4: How do you update control content after creation? A: Access the container via the control instance and modify its innerHTML.
Q5: How do you remove a control? A: Call map.removeControl(controlInstance).
Challenge: Build a toolbar with 4 buttons (Zoom In, Zoom Out, Center, Info). Position it at the top center of the map. Each button should perform its action and show a brief tooltip on hover.
FAQ
Try It Yourself
Build a map with a custom info control.
<!DOCTYPE html>
<html>
<head>
<title>Custom Controls Demo</title>
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9/dist/leaflet.css" />
<script src="https://unpkg.com/leaflet@1.9/dist/leaflet.js"></script>
<style>
.info-control { background: white; padding: 8px 14px; border-radius: 6px; box-shadow: 0 2px 6px rgba(0,0,0,0.2); font-size: 13px; cursor: default; }
.info-control button { margin-left: 4px; cursor: pointer; border: 1px solid #ddd; background: white; border-radius: 4px; padding: 4px 10px; }
.info-control button:hover { background: #f0f0f0; }
.legend-box { display: inline-block; width: 12px; height: 12px; margin-right: 4px; border-radius: 2px; }
</style>
</head>
<body style="font-family:sans-serif;padding:20px;">
<h2>Custom Controls</h2>
<div id="map" style="height:400px;border-radius:8px;"></div>
<script>
var map = L.map('map').setView([40.7128, -74.0060], 12);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '© OSM'
}).addTo(map);
var LegendControl = L.Control.extend({
options: { position: 'bottomright' },
onAdd: function() {
var div = L.DomUtil.create('div', 'info-control');
div.innerHTML = '<b>Legend</b><br><span class="legend-box" style="background:#4ecdc4;"></span>Water<br><span class="legend-box" style="background:#2a9d8f;"></span>Parks<br><span class="legend-box" style="background:#ff6b35;"></span>Urban';
return div;
}
});
var CenterControl = L.Control.extend({
options: { position: 'topleft' },
onAdd: function(map) {
var container = L.DomUtil.create('div', 'info-control leaflet-bar');
container.innerHTML = '<button id="center-btn">Center NYC</button>';
L.DomEvent.on(container, 'click', function() {
map.setView([40.7128, -74.0060], 12);
});
L.DomEvent.disableClickPropagation(container);
return container;
}
});
map.addControl(new LegendControl());
map.addControl(new CenterControl());
</script>
</body>
</html>
What's Next
Build custom popups.
Popups — Custom popup content. Measurement — Measurement tools.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro