⚡ Modernization of Leaflet
After two and a half years of hard work, we’re thrilled to announce the first alpha release of Leaflet 2.0!
This release marks a major modernization of the Leaflet codebase. We've dropped support for Internet Explorer, removed legacy methods and polyfills, adopted modern standards like Pointer Events, and now publish Leaflet as an ESM module. The global L
is no longer part of the core package (though it’s still available in the bundled version leaflet-global.js
for backward compatibility).
For more information checkout the blog post: https://leafletjs.com/2025/05/18/leaflet-2.0.0-alpha.html
What's New in Leaflet 2.0
- Targeting only evergreen browsers
- Switched from
Mouse
andTouch
events toPointerEvents
- ESM support and tree shaking
- Rewritten using standardized ES6 classes
Migration
- Replace all factory methods with constructor calls:
L.marker(latlng)
➜new Marker(latlng)
- Change the
<script>
tag tomodule
:<script type='module'>
- Replace usage of
L
with explicit imports from the Leaflet package:import { Marker } from 'leaflet'
- if you are not using a package manager like npm, you can use a
importmap
: https://leafletjs.com/examples/quick-start/
- if you are not using a package manager like npm, you can use a
- To have global access to variables of a
module-script
, assign them to thewindow
object (Not recommended):window.map = map
- Consider Leaflet V1 Polyfill if you are using outdated plugins
Old implementation
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
<script>
const map = L.map('map').setView([51.505, -0.09], 13);
const tiles = L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 19,
attribution: '© <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>'
}).addTo(map);
</script>
New implementation
Module
<script type="importmap">
{
"imports": {
"leaflet": "https://unpkg.com/leaflet@2.0.0-alpha1/dist/leaflet.js"
}
}
</script>
<script type="module">
import L, {Map, TileLayer, Marker, Circle, Polygon, Popup} from 'leaflet';
const map = new Map('map').setView([51.505, -0.09], 13);
const tiles = new TileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 19,
attribution: '© <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>'
}).addTo(map);
</script>
Global Script
<script src="https://unpkg.com/leaflet@2.0.0-alpha1/dist/leaflet-global.js"></script>
<script>
const map = new L.Map('map').setView([51.505, -0.09], 13);
const tiles = new L.TileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 19,
attribution: '© <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>'
}).addTo(map);
</script>
Need Legacy Support?
Check out this polyfill package to help ease the transition for legacy apps: Leaflet V1 Polyfill
Changes
❇️ New Features
- Automatically attributes OSM if OSM tiles are used and no attribution is provided by @Jouwee in #9489
- Added
decoding
option to ImageOverlay by @IvanSanchez in #8650 - Added option for setting value of
aria-label
for popup close button by @ShivangMishra in #8590 - Implement
BlanketOverlay
as Renderer superclass by @IvanSanchez in #8611 - Support SSR by @Falke-Design in #9385
- Implemented
trackResize
for L.Popup by @IvanSanchez in #9605 - VideoOverlay: add
controls
option by @simon04 in #9666 - Add
aria-keyshortcuts
to map container by @chcederquist in #9688 - Expand layers control on Enter keydown by @larsgw in #8556
✨ Refactorings (⚠️ Breaking Changes)
- Change
<section>
to<fieldset>
in the layers control by @tmiaa in #7912 - Use ResizeObserver for map resizes by @IvanSanchez in #8612
- Remove usage of global L and safeguard against it by @mourner in #8536
- Replace
Util.bind()
withFunction.bind()
by @jonkoops in #8484 - Replace
Util.isArray()
withArray.isArray()
by @jonkoops in #8683 - Replace
Util.create()
withObject.create()
by @jonkoops in #8681 - Replace
Util.trim()
withString.prototype.trim()
by @jonkoops in #8682 - Prefer
Object.hasOwn()
overObject.prototype.hasOwnProperty()
by @jonkoops in #8684 - Use the
classList
API for class manipulation methods inDomUtil
by @jonkoops in #8685 - Replace
DomUtil.hasClass()
withclassList.contains()
by @jonkoops in #8727 - Replace
DomUtil.getClass()
withclassList.value
by @jonkoops in #8728 - Remove
DomUtil.setClass()
method by @jonkoops in #8729 - Replace
DomUtil.addClass()
withclassList.add()
by @jonkoops in #8731 - Replace
DomUtil.removeClass()
withclassList.remove()
by @jonkoops in #8732 - Replace
DomUtil.remove()
withElement.remove()
by @jonkoops in #8735 - Replace
DomUtil.empty()
withElement.replaceChildren()
by @jonkoops in #8736 - Hold element positions in WeakMap by @jonkoops in #8749
- Destructure
statics
andincludes
from class properties by @jonkoops in #8823 - Enforce
.js
file extension for module imports by @jessetane in #8837 - Move
callInitHooks()
to prototype ofClass
by @jonkoops in #8825 - Use
Object.setPrototypeOf()
to extendClass
by @jonkoops in #8824 - Use Pointer Events for
Map.Keyboard
by @Falke-Design in #8758 - Convert
Class
to a JavaScriptclass
by @jonkoops in #8871 - Drop
__super__
property from Class by @jonkoops in #8800 - Move initialization logic for sub-classes to
Class
constructor by @jonkoops in #8872 - Use Pointer Events for
Map.BoxZoom
by @jonkoops in #8757 - Use Pointer Events for
Control.Layers
by @Falke-Design in #8759 - Drop UMD and make ESM the default entrypoint by @jonkoops in #8826
- Replace animation frame polyfill with native API by @jonkoops in #8810
- Rename
TouchZoom
handler toPinchZoom
(keep TouchZoom as alias for backward compatibility) by @Mahendra-006 in #9642 - Use nullish coalescing and optional chaining by @simon04 in #9661
- Use arrow callback and shorthand methods by @simon04 in #9659
- Replace
Util.getParamString
withURL.searchParams
by @simon04 in #9654 - Convert
Bounds
,LatLng
,LatLngBounds
,Point
,Transformation
to ES6 classes by @simon04 in #9655 - Use for...of by @simon04 in #9653
- Replace
Util.extend
with ES6 Object.assign or object spread by @simon04 in #9652 - Refactor to
PointerEvents
by @Falke-Design in #9620 - Convert
CRS
to ES6 classes by @simon04 in #9669 - Use SVG icon for layer control by @simon04 in #9665
- Optimize PNG files by @dilyanpalauzov in #8661
- Add shadow-icon as svg icon and add white circle to marker-icon by @Falke-Design in #8768
❌ Removed Features (⚠️ Breaking Changes)
- Drop legacy VML code & official support for IE7–8 by @mourner in #8196
- Remove the long-deprecated
L.Mixin.Events
and_flat
by @mourner in #8537 - Remove Internet Explorer from browser detection by @jonkoops in #8559
- Remove legacy CSS hacks and unused prefixes by @mourner in #8600
- Remove Microsoft-specific Pointer Events from browser detection by @jonkoops in #8603
- Remove Android from browser detection by @jonkoops in #8607
- Remove Edge from browser detection by @jonkoops in #8606
- Remove Opera from browser detection by @jonkoops in #8621
- Remove PhantomJS from browser detection by @jonkoops in #8622
- Remove
indexOf()
function from Util by @jonkoops in #8623 - Stop inheriting
filter
CSS property on tiles by @jonkoops in #8651 - Remove
DomUtil.setOpacity
by @mourner in #8730 - Remove
DomUtil.TRANSFORM
constant by @jonkoops in #8733 - Remove
TRANSITION
andTRANSITION_END
constants fromDomUtil
by @jonkoops in #8734 - Remove vendor prefixes when setting
userSelect
style by @jonkoops in #8738 - Remove
DomUtil.testProp()
by @jonkoops in #8739 - Remove
DomUtil.getStyle()
by @jonkoops in #8747 - Remove
Browser.any3d
by @jonkoops in #8748 - Remove SVG feature detection code by @jonkoops in #8755
- Remove
noConflict()
by @jonkoops in #8753 - Remove browser specific detection code for CSS transforms by @jonkoops in #8751
- Remove
L_NO_TOUCH
global switch by @jonkoops in #8752 - Remove
Browser.canvas
feature detection by @jonkoops in #8754 - Remove Windows platform detection by @jonkoops in #8769
- Remove Gecko-based browser detection by @jonkoops in #8770
- Remove references to PhantomJS by @jonkoops in #8771
- Remove passive event detection by @jonkoops in #8772
- Remove WebKit-based browser detection by @jonkoops in #8773
- Remove deprecated
which
and changebutton
to main-button by @Falke-Design in #8796 - Remove jitter debug page by @jonkoops in #8829
- Remove legacy timer fallbacks for
requestAnimFrame
by @mourner in #9236 - Get rif of prefixed
requestAnimationFrame
leftovers by @mourner in #9241 - Removes mozEvent warning by @Falke-Design in #9650
- Remove deprecated methods / options by @Falke-Design in #9622
- Removes factory functions by @Falke-Design in #9626
- Bring back global
L
as new bundleleaflet-global.js
by @Falke-Design in #9686
🐞 Bugfixes
- Fix Events.listens for nested propagations by @Falke-Design in #8457
- Fix marker popup location by @raychanks in #8523
- Fix intermittent wobble when setMaxBounds(map.getBounds()) by @rjackson in #8534
- Alternate fix for PopUp keepInView recursion and speed up associated test by @rjackson in #8520
- Support sticky maps by @tmiaa in #8550
- Align the scale control's alpha transparency with the attribution control by @Malvoz in #8547
- Allow the scale control's text to overflow the container by @Malvoz in #8548
- Fixes event target of popupopen event and adds test by @Belair34 in #8571
- Fix worldCopyJump with Keyboard by @Falke-Design in #8562
- Fix CSS syntax error in leaflet.css by @Malvoz in #8628
- Fix closed coord's reference in latLngsToCoords by @marlo22 in #7344
- Rename 'closed' parameter of 'latLngsToCoords' to 'close' to avoid confusion by @ronaldhoek in #8678
toGeoJSON()
should still work if no values in coords array by @Falke-Design in #8737- Fix implementation for disabling & enabling text selection by @jonkoops in #8741
- fix vector drifts when
zoomAnimation
isfalse
and zooming viaflyTo
or pinch by @plainheart in #8794 - Update PolyUtil.js by @davidmgvaz in #8840
- Revamp synthetic dblclick event instantiation by @IvanSanchez in #8632
- Alleviate tile gaps in chromium by using mix-blend-mode CSS by @IvanSanchez in #8891
- Apply mix-blend-mode only on tile images by @Falke-Design in #8907
- Set
outlineStyle
instead ofoutline
when preventing outline by @jonkoops in #8917 - Prevent adding layer while expanding Layer-Control on mobile by @Falke-Design in #8910
- Fix tooltip focus listener if getElement is no function by @Falke-Design in #8890
- Fix issue whereby tooltips loaded dynamically while moving the map were never shown. by @theGOTOguy in #8672
- Fix noMoveStart option for fitBounds method by @AbdullahSohail-SE in #8911
- Mapbox tiles not loading 8960 by @Dele-Oyelese in #8968
- Update the height of the container after resizing the window by @Falke-Design in #9019
- Use _limitZoom in flyTo, like we do in resetView by @yohanboniface in #9025
- Do not propagate click when element has been removed from dom by @yohanboniface in #9052
- Fixes showing tooltip while panning the map by @Falke-Design in #9154
- Fix hover underline in flag + Leaflet attribution prefix by @rkaravia in #9280
- Prevent showing outline-box on Chromium when clicking on a vector with a tooltip by @Falke-Design in #9393
- Clear timeouts on remove (#9575) by @Mark-Falconbridge-i2 in #9577
- Fix Leaflet attribution link by @florian-h05 in #9471
- Solution to issue 9067 - Inverting x axis of L.CRS.Simple causes L.Circle to have no radius by @bakp22 in #9414
- Unbind tooltip remove focus listeners by @hollowM1ke in #9232
- Refocus map after using layers control (#9004) by @quarl in #9005
- GridLayer construct url with integer {z} for fractional zoom by @raychanks in #8613
- Fix adding Icon.Default shadow icon to the map if option is falsy by @Falke-Design in #9607
- Clean up DOM event listeners when destroying Map's animation proxy by @samclaus in #9048
- Fix Canvas rendering with setting _redrawRequest to null for requestAnimFrame by @Falke-Design in #9608
- Add dashOffset to Canvas by @Falke-Design in #9649
- Add cancelable check before preventDefault while dragging by @Falke-Design in #9639
- Fix video control / seek bar usage in safari by @Falke-Design in #9641
- GeoJSON: fix object spread regression by @simon04 in #9678
- Use arrow callback by @simon04 in #9684
- TileLayer.WMS: fix wmsParams parameter regression by @simon04 in #9683
📝 Docs
- Update website & docs for v1.9.0 by @jonkoops in #8453
- Fix the resource integrity hashes by @jonkoops in #8456
- README: update JS size for 1.9.1 by @simon04 in #8468
- Deprecate old versions' docs by @Malvoz in #8294
- Use the topmost browsing context for links in tutorial frames by @Malvoz in #8466
- Update the Leaflet Editor's
description
and mapid
by @Malvoz in #8476 - Quick-start: fix link in code block. by @Sjlver in #8415
- fixing typo lon->lng in refence.html file by @shashwat010 in #8497
- chore: replaced
substr
withsubstring
by @k-rajat19 in #8517 - Update documentation for v1.9.2 by @jonkoops in #8527
- Update changelog with latest revisions by @jonkoops in #8528
- Adding documentation for the support of lon in the latLng function, resolves 8509 by @brianferry in #8524
- Fixed some grammers in readme by @Saran-pariyar in #8539
- Fix 2 links without URLs in docs by @Malvoz in #8542
- Fix markdown link of
ImageOverlay.decoding
by @plainheart in #8660 - Update Changelog 1.9.3 - main by @Falke-Design in #8662
- Minor typo in code example by @Lalaluka in #8710
- Added missing curly bracket in docs by @i-sukhanov in #8767
- Update docs language for HTML and CSS. by @alope107 in #8934
- CHANGELOG.md - Add 1.9.4 by @mtmail in #8967
- Update stackoverflow image #9023 by @Beast-Hunter in #9030
- Updated Twitter Logo and Name by @aialok in #9102
- Updated Multiple Logos by @UmerrAli in #9136
- Update License to 2024 by @arnabsen in #9219
- Changed Wording by @hollowM1ke in #9229
- remove dead links in #9305
- Add citation.cff to repo by @nakajimayoshi in #9341
- Docstrings for L.Marker getElement() by @yuri-karelics in #9180
- Add Stadia Maps to FAQ by @ianthetechie in #9340
- Removed unused variable from the choropleth example doc by @Cinderella-Man in #9182
- Add scroll up button to website by @simondriesen in #9186
- Add scrollbar to container if the text can't wrapped on mobile devices by @Falke-Design in #9384
- Update CONTRIBUTING.md by @cherylhughey in #9435
- Update FAQ.md by @cherylhughey in #9436
- Update License to 2025 by @PixlePixle in #9597
- Evergreen language updates and removal by @Kxiru in #9528
- Changed slogan with evergreen wording #8477 by @JoT8ng in #9491
- Updated Evergreen language in the documentation #8477 {please label me for Hacktoberfest-24} by @Dhairya-A-Mehra in #9488
- Update browser support list by @jonkoops in #8455
- Create SECURITY.md by @Ahlam-Banu in #9619
- Refactor docs for ESM by @Falke-Design in #9624
- Improve documentation about renderer and pane by @Falke-Design in #9651
- Replace unpkg with jsDelivr in documentation (fixes #9628) by @eshanair in #9663
- Remove dead link(Placekitten.com) for Cataas(fixes #9691) by @MelvinManni in #9695
- Updated titles to be descriptive, aligned tutorial titles with overvi… by @chcederquist in #9692
- Add
position
doc string to controls by @Falke-Design in #8570
📜 Formatting
- Enforce
quotes
ESLint rule for thespec
directory by @jonkoops in #8686 - Provide file extensions when running ESLint by @jonkoops in #8831
- Lint files in
debug
directory by @jonkoops in #8925 - Upgrade to ESLint 9+ and flat config by @mourner in #9410
- Enable
prefer-exponentiation-operator
linting rule and fix issues by @simon04 in #9660 - Guard for-in loops and enable guard-for-in lint rule. by @alope107 in #8879
- Split ESLint config and clean it up by @jonkoops in #8563
- Upgrade ESLint config to latest version by @jonkoops in #8583
- Enable
arrow-spacing
linting rule and fix issues by @jonkoops in #8584 - Enable
func-name-matching
linting rule and fix issues by @jonkoops in #8585 - Enable
no-duplicate-imports
linting rule and fix issues by @jonkoops in #8586 - Enable
prefer-template
linting rule and fix issues by @jonkoops in #8587 - Enable
prefer-rest-params
linting rule and fix issues by @jonkoops in #8593 - Enable
object-shorthand
linting rule and fix issues by @jonkoops in #8592 - Enable
prefer-arrow-callback
linting rule and fix issues by @jonkoops in #8594 - Enable
no-var
linting rule and fix issues by @jonkoops in #8602 - Remove unused test globals from ESLint config by @jonkoops in #9285
🔧 Workflow
- Improve integrity generation for releases by @mourner in #8459
- Remove the release check to manually modify the version number in
reference.html
by @Malvoz in #8475 - Use different cache keys by @jonkoops in #8487
- Add build-docs job to main github actions file by @exequiel09 in #8504
- Add step for release assets by @IvanSanchez in #8494
- Add Dependabot config for Bundler dependencies by @jonkoops in #8516
- tests workflow for mac and win by @adrianaris in #8540
- Upgrade Rollup to version 3 by @jonkoops in #8582
- Convert build scripts into ESM by @jonkoops in #8610
- Use Node.js version 18 for CI by @jonkoops in #8640
- Add a note to release docs to ensure green CI by @mourner in #8668
- Run CI on Ubuntu 20.04 by @jonkoops in #8670
- Simplify workflows on CI by @mourner in #8671
- Upgrade Bundlemon to v2 by @jonkoops in #8676
- Lock
actions/cache
to version 3.2.0 by @jonkoops in #8742 - Run CI on latest version of Ubuntu by @jonkoops in #8744
- Run CI on latest version of Windows by @jonkoops in #8743
- Use OS name as part of cache key for jobs by @jonkoops in #8750
- Always build ESM version in watch mode by @jonkoops in #8844
- Add dev dependency http-server for debugging ESM by @jessetane in #8848
- Regenerate lockfile in version 3 format by @jonkoops in #8919
- Remove Rollup pre-proccesor from Karma runner by @jonkoops in #8935
- Add possibility to create coverage reports by @Falke-Design in #9029
- Include
prosthetic-hand
from GitHub by @jonkoops in #9033 - Upgrade and cleanup dev dependencies by @mourner in #9157
- Set
versioning-strategy
for NPM toincrease
by @jonkoops in #9165 - Speed up Karma runner by narrowing down files Karma serves by @mourner in #9231
- Upgrade Husky to latest version by @jonkoops in #9264
- Run test with the source files by @Falke-Design in #9609
- Add missing "./dist/leaflet.css" specifier in "leaflet" package by @simon04 in #9658
- Address npm audit issues by @Falke-Design in #9714
- Replace
version
whennpm version
is called by @Falke-Design in #9717 - Remove Karma launcher for Microsoft Edge by @jonkoops in #8604
- Remove Internet Explorer from test framework by @jonkoops in #8264
🧪 Tests
- Disable zoom animation for Line/PolyUtil tests by @mourner in #8478
- Fix test on mac
changes the option 'wheelPxPerZoomLevel'
by @Falke-Design in #8481 - Make test runner output cleaner by @mourner in #8480
- Add slow test stats and make some tests faster by @mourner in #8486
- Added tests for panInsideBounds by @mikelowe5919 in #8429
- Added testing for mouseEventLatLng by @spatterss135 in #8403
- Update tests
index.html
and add missing RectangleSpec by @Falke-Design in #8499 - Add test for map stopLocate (#8371) by @raychanks in #8505
- Added test cases for Map:addHandler method by @precious-void in #8503
- Added test cases for Map:mouseEventToContainerPoint method. by @kreloaded in #8406
- Cover Map Locate with Unit Tests by @stephenspol in #8424
- Add
getWheelPxFactor
and fix testchanges the option 'wheelPxPerZoomLevel'
for mac by @Falke-Design in #8512 - Speed up PathSpec's "Add layer inside move handler" test: 611ms to 5ms (122x faster) by @rjackson in #8518
- Speed up tests Map.ScrollWheelZoomSpec and Map.DoubleClickZoomSpec by @rjackson in #8519
- Add
panBy
test by @adrianaris in #8420 - Speed up tests relating to focusing on Marker by @rjackson in #8545
- Speed up TileLayer.setUrl test (251ms to 13ms) by @rjackson in #8546
- Speed up tests relating to containerPoint / layerPoint methods by @rjackson in #8544
- Adding tests for 'layerPointToLatLng' method #8375 by @ANaphade in #8435
- Refactor Event handling and happen.js by @Falke-Design in #8760
- Added two more test cases for the unproject map method by @snehalvibhute in #8637
- Replace expect.js with Chai by @jonkoops in #8952
- Import Leaflet in tests using JavaScript modules by @jonkoops in #8975
- Update
ui-event-simulator
and import as JavaScript module by @jonkoops in #8977 - Add tests for BoxZoom by @Falke-Design in #9032
- Fix CI tests to not depend on big Chrome window size by @mourner in #9235
- Use explicit imports
chai
andsinon
in the test suite by @jonkoops in #9284 - Set Chrome window size to fix failing test in ubuntu by @Falke-Design in #9604
- add test for project method by @AshwinNema in #9303
- Add demo for all GeoJSON types by @simon04 in #9679
- Vector Drift Test Pinch Zoom Fix by @stephenspol in #8644
- Convert control layers debug page to ESM by @jonkoops in #8832
- Convert canvas debug page to ESM by @jonkoops in #8830
- Convert geolocation debug page to ESM by @jonkoops in #8835
- Convert controls debug page to ESM by @jonkoops in #8834
- Convert map debug pages to ESM by @jonkoops in #8838
- Convert test debug pages to ESM by @jonkoops in #8921
- Convert vector debug pages to ESM by @jonkoops in #8924
- Use import maps to load Leaflet in the
debug
directory by @jonkoops in #8926 - flyToBounds tests added by @shreya024 in #9112
- Add test for map
stop
(#8369) by @victorpatru in #8581
Full Changelog: v1.9.4...v2.0.0-alpha