The first beta of Leaflet 2.0, and a big step up from the alpha. Leaflet is now fully tree-shakeable (a map with a tile layer is 25.6 kB gzipped, down from 38 kB), ships its own TypeScript types, and is built from plain ES classes. The default marker icon now works out of the box with any bundler and can be colored and rotated. Panning, zooming and rendering are smoother and faster, keyboard and screen reader support is much better, and over 40 bugs are fixed, including the infamous seams between tiles.
The release targets Baseline 2022 browsers (Safari 15.4+) and has more breaking changes since the alpha, listed after the improvements. Removed APIs throw an error or log a one-time warning that says what to use instead, and the migration guide covers every change.
❇️ New features
- Make Leaflet fully tree-shakeable, so apps only bundle what they use. A map with a tile layer is now 25.6 kB gzipped instead of 38 kB (#10436, #10437, #10438, #10439, #10441, #10442, #10443)
- Add TypeScript types, generated from the source and checked in strict mode, with typed options, per-class events, and module augmentation for plugins (#10496–#10510)
- Replace the default marker images with an inline SVG icon that works with any bundler or CDN with no configuration, ending the broken marker image problem (#4968). It can be colored with any CSS color:
new DefaultIcon({color: 'crimson'})(#10450, #10489, thanks to @Falke-Design for #8855) - Add marker rotation with the
rotationoption andsetRotation()/getRotation()(#10489)
⚡️ Performance
- Render the default marker icon 25–45% faster, with half the DOM nodes per marker (#10489)
- Stop restyling the whole page on every map or marker drag, which took up to 72 ms per press on large pages in Chromium (#10470)
- Create objects about 2× faster (#10432, #10433)
- Redraw vector layers 12–14% faster (#10494), and clip polygons 10–55% faster
- Avoid redundant relayouts during wheel and pinch zoom
- Minify the CSS (3.2 → 2.4 kB gzipped) and remove dead legacy code (#10493, #10431, #10452, #10490)
♿️ Accessibility
- Respect
prefers-reduced-motionby toning motion down: big zooms and pans happen instantly,flyTodoesn't zoom out and back in, and inertia is shorter (#10250 by @IvanSanchez, #10466, #10479) - Make markers keyboard-accessible: Enter and Space click a focused marker, and arrow keys move a draggable one (#10473, #10513 by @Enfiladeguide)
- Make keyboard zoom work on any keyboard layout:
+used to zoom out on Nordic layouts (#10469, based on #10096 by @babielmam) - Give the map container
role="region"andaria-label="Map"unless already set (#10521 by @nfreear) - Add an accessible name to the layers control list (#9116 by @creperi)
✨ Improvements
- Draw vector paths with subpixel precision, so adjacent polygons line up without gaps and thin shapes don't zigzag (#10494)
- Keep old tiles visible instead of flashing grey when zooming without animation, and stop wheel and keyboard zoom from running away (#10465)
- Limit inertia speed by default (
inertiaMaxSpeed: 1400), so a fast flick no longer throws the map many screens away (#10479) - Keep dragging when the pointer leaves the window, and support dragging in maps opened in another window (#10133 by @markmeeus, #10150 by @IvanSanchez)
- Lower the default polyline
smoothFactorto 0.5, reducing slivers between adjacent shapes (#10087 by @IvanSanchez) - Restore the
layerproperty on events that bubble up to a group, as an alias ofpropagatedFrom. It was removed in 2.0.0-alpha and broke common code without an error - Warn when listening to removed events like
mousedownorkeypress, throw when aCirclegets the radius as the second argument, and acceptbubblingMouseEventsas an alias ofbubblingPointerEvents(#9866 by @simon04) - Default to
referrerPolicy: 'strict-origin-when-cross-origin'for tiles and upgradehttp://OpenStreetMap URLs to https, as the OSM tile policy requires (#9897 by @IvanSanchez, #10444) - Prevent the iOS save-image menu on long press on markers (#10274 by @hassams)
- Support updating
Circleradius withsetStyle({radius})(#9853 by @AbbasReads) - Apply changes to
keyboardPanDeltaandzoomDeltaafter map creation (#10490) - Make
Draggablework outside a map
⚠️ Breaking changes
- Remove the default export: use
import * as L from 'leaflet'or named imports instead ofimport L from 'leaflet'(#10029 by @jonkoops) - Export all classes under flat names:
Control.Zoom→ZoomControl,TileLayer.WMS→WMSTileLayer,Icon.Default→DefaultIcon,CRS.EPSG3857→EPSG3857,Projection.Mercator→MercatorProjection,Map.Drag→DragHandler, and so on (#10054, #10055, #10056, #10057, #10058, #10059, #10063 by @simon04) - Ship only minified builds in
dist:leaflet.js(ESM) andleaflet-global.js(globalL, without AMD/CommonJS support orL.noConflict()), with source maps. The-srcbuilds and theimagesfolder are gone (#10443, #10027 by @jonkoops) - Stop resolving deep imports from
leaflet/src/...andleaflet/dist/..., exceptleaflet/dist/leaflet.css(also available asleaflet/styles.css) (#9910 by @jonkoops) - Replace
@types/leafletwith types shipped in the package. Type names are flat (Control.LayersOptions→LayersControlOptions), events are typed per class (noLeafletMouseEvent), and generic parameters are options and events rather than GeoJSON properties (#10496–#10510) - Change the default icon size from 25×41 to 25×46 with the shadow included, and remove
imagePath,DefaultIcon.imagePathandshadowUrl(#10450, #10489) - Remove the
preferCanvasmap option; userenderer: new Canvas()instead (#10438) - Replace
initialize()withconstructor()andsuper.initialize(...)withsuper(...)in subclasses, and removeClass.extend()in favor ofclass X extends Y. Init hooks now run once on the fully constructed instance (#10006 by @jonkoops, #10433 based on #10069 by @simon04 and #10179 by @reblace) - Remove
X.prototype.options: change defaults withX.mergeOptions({...})(e.g. instead ofMarker.prototype.options.icon = ...), and declare them in subclasses withstatic defaultOptions = {...}.setDefaultOptionsis deprecated (#10432, based on #10069 by @simon04 and #10179 by @reblace) - Remove the
Browsernamespace. UsematchMedia('(pointer: coarse)').matchesinstead ofBrowser.mobile;Browser.touchwas always true. Theleaflet-safariclass is gone, and thetapHoldhandler is now on by default (#10447 with @IvanSanchez) - Remove the
keypressevent; usekeydown(#10473) - Set
leaflet-draggingon the map container instead of<body>, and make map content unselectable with CSS. Text in custom panes, or in controls placed outside the control container, needsuser-select: textto be selectable (#10470) - Stop setting
aria-keyshortcutsby default, since it was only right for US keyboards; opt in with the newariaKeyShortcutsoption.KeyboardHandler.keyCodesis nowkeysand holdsKeyboardEvent.keyvalues (#10469, based on #10096 by @babielmam) - Pass GeoJSON
filterandonEachFeaturea Feature (the same object aslayer.feature) when the input is a bare geometry, and skip features with anullgeometry - Return fractional pixel positions from
latLngToLayerPointandlatLngToContainerPoint(#10494) - Make
setView(center, zoom, true)no longer force a view reset (#10507) - Make
closeTooltip()return the layer, andisTooltipOpen()returnfalsewith no tooltip bound - Make
Popup,TooltipandDivOverlayno longer extendLayer, soLayer.include(),addInitHook()andmergeOptions()don't affect them (#10441) - Make
DomEventfunctions return nothing instead ofDomEvent(#10501) - Remove legacy and internal utilities:
DomUtil.preventOutline/restoreOutline, and the third argument ofDraggable(#10455)DomUtil.disableTextSelection/enableTextSelection; useuser-select: none(#10470)DomEvent.getPropagationPath(useevent.composedPath()),PointerEvents,getWheelPxFactorandisExternalTarget(#10431, #10501)PosAnimation(#10446)LayerGroup.invoke; useeachLayer(#9680 by @simon04)Util.emptyImageUrl(#10068 by @SerAbin1)_leaflet_*properties on DOM elements; useDomEvent.disableClickPropagation(#10501)
🐞 Bug fixes
- Fix seams between tiles during zoom, at fractional zoom levels, and while dragging, a problem since 2015 (#10471, #10445, #3575)
- Fix vector layers rendering shifted or blurry, a regression in alpha.1 (#9849, #9854 by @IvanSanchez)
- Fix the map drifting away from the cursor while zooming (#3737, #7926)
- Fix map panes and controls covering page elements like dialogs and dropdowns (#3690)
- Fix wheel zoom jumping 2 levels at a time in Chrome on Linux (#10447)
- Fix double-tap zoom on iOS 18.2+ (#10447)
- Fix the map jumping when starting a drag during inertia (#10386 by @Yahiro025)
- Fix dragging sometimes getting stuck after releasing the pointer outside the window
- Fix clicking a control scrolling the page to the map (#4125, #5392)
- Fix Escape sometimes not closing popups (#6551)
- Fix popups with
maxHeightnot scrolling or resizing with their content (#10487 by @nk-batman21) - Fix the attribution control keeping credits of removed layers (#10458 by @haranishi)
- Fix hover breaking when calling
bringToFrontonpointeroverin Chrome 144+ (#10129) - Fix
flyTothrowing for some coordinates (#10053 by @itslevo), bouncing back nearmaxBounds(#10477 by @Don-Yin), and shifting layers after a short flight (#9438) - Fix
worldCopyJumprebuilding all tiles (#10353 by @leijurv) and throwing with an infinite CRS (#10453 by @haranishi) - Fix marker dragging stopping on
setIcon(#10457 by @haranishi) and turning back on after re-adding a marker (#10456 by @haranishi) - Fix the layers control reverting layer changes made in its event handlers (#10374 by @A350xt)
- Fix focusing a marker with an
'auto'icon size throwing (#10375 by @A350xt) - Fix
WMSTileLayerthrowing on relative URLs (#10474 by @nk-batman21) and producing invalid URLs after changingversion(#10482 by @osiandhammaloka) - Fix
VideoOverlayignoringkeepAspectRatio: falsein Firefox and Safari (#10475 by @nk-batman21) and throwing when created from a<video>element (#10464 by @Enesuygurs) - Fix
Circle.getBounds()throwing before the circle is added (#6413 by @Schleuse), andFeatureGroup.getBounds()throwing in some cases (#7232 by @rafaelgallani) - Fix small circles drawn as ellipses (#10454 by @haranishi)
- Fix tiles being requested with a fractional zoom on fractionally zoomed maps (#9998 by @adamnson)
- Fix non-square tiles wrapping by their height instead of width (#10333 by @Jaybhade)
- Fix tiles removed during a zoom firing
tileerror(#10490) - Fix tiles, markers and tooltips being selectable in Safari
- Fix the Canvas renderer throwing when the map is removed in the same frame as a view change (#10522 by @nk-batman21)
- Fix an error when removing the map in a tile layer's
loadlistener (#10122 by @pierreeurope) - Fix a popup
autoPancrash when the popup is removed mid-pan (#10416 by @DeanNorman) - Fix
Infinityzoom after a tiny box zoom (#6709) - Fix an infinite loop in
LineUtil.clipSegmentwith fractional bounds (#10284 by @spokodev) - Fix
toGeoJSONdropping MultiPoints inside a GeometryCollection (#10468 by @nk-batman21) - Fix rebinding a tooltip with different
permanent/stickyoptions, and tooltip focus after re-adding a layer (#10490) - Fix pinch zoom and tap-hold leaking listeners when disabled mid-gesture (#10207)
- Fix an error when
subdomainsis undefined (#7744) - Fix
LatLng.validate(null)throwing (#10276 by @chatman-media), andclone()adding analtproperty (#10267 by @JSap0914) - Fix minor edge cases found by type checking, such as
removeLayerthrowing on an unknown id andGridLayer.isLoading()returningundefined(#10502, #10503)
📝 Docs
- Generate the API reference from the TypeScript types, fixing many inaccuracies along the way
- Build the website from
mainwith a small Node script instead of Jekyll (#10484, #10485) - Document handling untrusted HTML in popups and tooltips (#10205)
- Many smaller docs fixes and plugin list updates by the community