Leaflet Draw-a-Radius Area Selector — Free HTML CSS JS Snippet
Leaflet Draw-a-Radius Area Selector · Misc · Plain HTML, CSS & JS · Live preview
Copy & paste
Source Code
<div class="rs-wrap">
<div class="rs-bar">
<div class="rs-title">Service Area Selector</div>
<div class="rs-info" id="rsInfo">Click the map to set a center point.</div>
<label class="rs-slider-wrap">
Radius: <output id="rsRadiusOut">5</output> km
<input type="range" id="rsRadius" min="1" max="50" value="5">
</label>
</div>
<div class="rs-map" id="rsMap"></div>
</div>*{box-sizing:border-box;margin:0;padding:0}
body{font-family:system-ui,-apple-system,sans-serif}
.rs-wrap{height:100vh;min-height:480px;display:flex;flex-direction:column}
.rs-bar{padding:12px 16px;background:#fff;border-bottom:1px solid #e2e8f0;display:flex;align-items:center;gap:18px;flex-wrap:wrap}
.rs-title{font-size:13px;font-weight:800;color:#0f172a}
.rs-info{font-size:12px;color:#6366f1;font-weight:700;flex:1;min-width:180px}
.rs-slider-wrap{display:flex;align-items:center;gap:8px;font-size:12px;font-weight:600;color:#334155}
.rs-slider-wrap output{font-weight:800;color:#6366f1;min-width:20px}
.rs-slider-wrap input{width:130px;accent-color:#6366f1}
.rs-map{flex:1}
.rs-pin-icon{background:#6366f1;width:12px;height:12px;border-radius:50%;border:2px solid #fff;box-shadow:0 1px 4px rgba(0,0,0,.5)}var map = L.map('rsMap').setView([37.775, -122.42], 11);
L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer/tile/{z}/{y}/{x}', {
attribution: 'Tiles © Esri',
maxZoom: 19,
}).addTo(map);
var centerMarker = null;
var circle = null;
var radiusKm = 5;
var radiusInput = document.getElementById('rsRadius');
var radiusOut = document.getElementById('rsRadiusOut');
var infoEl = document.getElementById('rsInfo');
var pinIcon = L.divIcon({ className: 'rs-pin-icon', iconSize: [12, 12] });
function formatArea(km) {
var areaKm2 = Math.PI * km * km;
return areaKm2 >= 100 ? Math.round(areaKm2).toLocaleString('en-US') : areaKm2.toFixed(1);
}
function updateInfo(latlng) {
infoEl.textContent = latlng
? radiusKm + ' km radius \u2014 covers about ' + formatArea(radiusKm) + ' km\u00b2. Drag the pin or click elsewhere to move it.'
: 'Click the map to set a center point.';
}
function placeOrMove(latlng) {
if (!circle) {
// L.circle takes a radius in METERS regardless of the map's zoom level --
// unlike a plain SVG circle, its on-screen size is computed from real
// geographic distance, so it stays the correct physical size as you zoom.
circle = L.circle(latlng, { radius: radiusKm * 1000, color: '#6366f1', weight: 2, fillOpacity: 0.15 }).addTo(map);
centerMarker = L.marker(latlng, { icon: pinIcon, draggable: true }).addTo(map);
centerMarker.on('drag', function (e) { circle.setLatLng(e.target.getLatLng()); updateInfo(e.target.getLatLng()); });
} else {
circle.setLatLng(latlng);
centerMarker.setLatLng(latlng);
}
updateInfo(latlng);
}
map.on('click', function (e) { placeOrMove(e.latlng); });
radiusInput.addEventListener('input', function () {
radiusKm = Number(radiusInput.value);
radiusOut.textContent = radiusKm;
if (circle) {
circle.setRadius(radiusKm * 1000);
updateInfo(circle.getLatLng());
}
});Extend with AI
Take This Snippet Further With AI
The AI prompt for this one is for Patreon supporters ($3 a month), along with a site with no ads. Supporters enter the passcode once and it is remembered in this browser.
What's included
Features
About this UI Snippet
Leaflet Draw-a-Radius Area Selector — a Circle Sized in Real Meters

A delivery radius, a service area, a search-within-distance filter — all of them need the same interaction: let someone pick a center point and a distance, and show them exactly what that covers on a map. The tricky part isn't the UI, it's that the circle has to represent a real, consistent geographic distance at every zoom level, not a fixed pixel size.
L.circle takes a radius in meters, not pixels
This is the detail that makes the whole snippet work: L.circle(latlng, { radius: radiusKm * 1000 }) specifies the circle's radius as a real-world distance in meters. Leaflet recomputes its on-screen pixel size every time you zoom, so a 5 km circle always represents 5 actual kilometers on the ground — zoom out and it shrinks on screen while staying 5 km in reality, exactly the behavior a service-area tool needs and a plain SVG circle (sized in pixels) could never provide.
The marker and circle share one drag handler
Once placed, the center marker is draggable, and its drag event handler calls circle.setLatLng() on every movement — not just on drop — so the circle visibly follows the pin in real time instead of snapping into place only after the drag ends. Both the info text and the circle's position update from the same coordinate on every step.
The radius slider updates the existing circle, not a new one
Moving the slider calls circle.setRadius() on the already-placed circle object rather than removing and recreating it — cheaper, and it avoids a visible flicker or the map losing the circle's current center during a rapid slider drag.
Area is computed from the same radius state
The displayed coverage area (Math.PI * km * km) is derived directly from the same radiusKm variable driving the circle itself, so the two numbers — the circle you see and the area figure in the text — can never drift apart or show a stale value from before the last slider move.
Reusing it
This is the base interaction for any radius-based area tool — delivery zones, real estate search radius, "find nearby" filters, broadcast or signal coverage estimates. Swap the info text's copy and hook the finished { lat, lng, radiusKm } values into whatever backend query or filter needs them.
Step by step
How to Use
- 1Add the Leaflet CDNLoad leaflet.css and leaflet.js before the snippet's JS runs.
- 2Paste HTML, CSS, and JSAn empty map renders with a radius slider above it.
- 3Click anywhere on the mapA pin and a circle appear centered on that point.
- 4Drag the pinThe circle follows in real time as you move it.
- 5Adjust the radius sliderThe circle resizes to the new true geographic distance.
- 6Zoom in or outThe circle stays the correct real-world size on screen.
Real-world uses
Common Use Cases
areaKm2) is derived from the same state as the circle, so the numbers and the map never disagree.Got questions?
Frequently Asked Questions
L.circle is created with its radius specified in real-world meters, not screen pixels. Leaflet recalculates the circle's on-screen pixel size from that real distance every time the map's zoom level changes, so a circle representing 5 kilometers on the ground always represents 5 kilometers regardless of how zoomed in or out the view currently is — unlike an SVG circle drawn with a fixed pixel radius, which would visually represent a different real distance at every zoom level.
The marker's drag event (which fires continuously throughout the drag gesture, not just once on release) calls circle.setLatLng() with the marker's current position on every single movement. Using dragend instead would only update the circle once the drag finishes, producing a pin that visibly detaches from its circle mid-drag and then snaps them back together at the end.
The handler calls circle.setRadius() on the already-existing circle object, which updates its geometry in place. Removing and re-adding a new circle on every slider input event (which can fire dozens of times during one drag) would be wasteful and could introduce a visible flicker or momentarily lose the circle's current center position.
The area text is computed with the standard circle-area formula (pi times radius squared) applied directly to the same radiusKm variable that also drives the circle's actual radius. Because both the visual circle and the text read from one shared value, there is no way for the displayed area to describe a different radius than what is actually drawn on the map.
Keep the center coordinate and radius in component state, initialize the Leaflet map and circle once in a mount effect, and update the circle's position/radius from state changes via setLatLng/setRadius rather than recreating the circle object on every render. Read the final { lat, lng, radiusKm } values from state whenever your app needs to submit or query against the selected area.