Ctrl + k

or run

tessl search
Log in

Version

Workspace
tessl
Visibility
Public
Created
Last updated
Describes
npmpkg:npm/react-leaflet@5.0.x

docs

index.md
tile.json

tessl/npm-react-leaflet

tessl install tessl/npm-react-leaflet@5.0.3

React components for Leaflet maps

controls.mddocs/reference/

Map Controls

UI control components for map interaction including zoom controls, scale display, attribution, and layer switching.

Related Documentation

  • Map Container - Disabling default controls
  • Hooks - Creating custom controls with hooks
  • Core APIs - createControlComponent for custom controls
  • Layer Groups - LayersControl with layer groups

Capabilities

LayersControl Component

Control for switching between base layers and toggling overlay layers.

/**
 * Component for layer switching control (compound component with sub-components)
 * @param props - Layers control properties
 */
const LayersControl: ForwardRefExoticComponent<
  LayersControlProps & RefAttributes<Control.Layers>
> & {
  BaseLayer: FunctionComponent<ControlledLayerProps>;
  Overlay: FunctionComponent<ControlledLayerProps>;
};

interface LayersControlProps extends Control.LayersOptions {
  /** Child BaseLayer and Overlay components */
  children?: ReactNode;
}

Usage Example:

import { MapContainer, TileLayer, LayersControl, Marker } from "react-leaflet";

function Map() {
  return (
    <MapContainer center={[51.505, -0.09]} zoom={13}>
      <LayersControl position="topright">
        <LayersControl.BaseLayer checked name="OpenStreetMap">
          <TileLayer
            attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>'
            url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
          />
        </LayersControl.BaseLayer>
        <LayersControl.BaseLayer name="Satellite">
          <TileLayer
            attribution='Tiles &copy; Esri'
            url="https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}"
          />
        </LayersControl.BaseLayer>
        <LayersControl.Overlay name="Markers">
          <Marker position={[51.505, -0.09]} />
        </LayersControl.Overlay>
      </LayersControl>
    </MapContainer>
  );
}

LayersControl.BaseLayer

Sub-component for defining base layers (only one can be active at a time).

/**
 * Base layer option in LayersControl (radio button behavior)
 * @param props - Controlled layer properties
 */
const BaseLayer: FunctionComponent<ControlledLayerProps>;

interface ControlledLayerProps {
  /** Whether this layer is initially checked */
  checked?: boolean;
  /** Child layer component (TileLayer, etc.) */
  children: ReactNode;
  /** Display name in the control */
  name: string;
}

LayersControl.Overlay

Sub-component for defining overlay layers (multiple can be active).

/**
 * Overlay layer option in LayersControl (checkbox behavior)
 * @param props - Controlled layer properties
 */
const Overlay: FunctionComponent<ControlledLayerProps>;

Usage Example:

<LayersControl>
  <LayersControl.Overlay checked name="Cities">
    <LayerGroup>
      <Marker position={[51.505, -0.09]} />
      <Marker position={[51.51, -0.1]} />
    </LayerGroup>
  </LayersControl.Overlay>
  <LayersControl.Overlay name="Roads">
    <Polyline positions={roadPath} />
  </LayersControl.Overlay>
</LayersControl>

ZoomControl Component

Zoom in and zoom out buttons.

/**
 * Component for zoom control buttons
 * @param props - Zoom control properties
 */
const ZoomControl: FunctionComponent<ZoomControlProps>;

type ZoomControlProps = Control.ZoomOptions;

Usage Example:

import { MapContainer, TileLayer, ZoomControl } from "react-leaflet";

function Map() {
  return (
    <MapContainer
      center={[51.505, -0.09]}
      zoom={13}
      zoomControl={false} // Disable default zoom control
    >
      <TileLayer url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" />
      <ZoomControl position="bottomright" />
    </MapContainer>
  );
}

ScaleControl Component

Displays the map scale in metric and/or imperial units.

/**
 * Component for displaying map scale
 * @param props - Scale control properties
 */
const ScaleControl: FunctionComponent<ScaleControlProps>;

type ScaleControlProps = Control.ScaleOptions;

Usage Example:

import { MapContainer, TileLayer, ScaleControl } from "react-leaflet";

function Map() {
  return (
    <MapContainer center={[51.505, -0.09]} zoom={13}>
      <TileLayer url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" />
      <ScaleControl position="bottomleft" metric={true} imperial={false} />
    </MapContainer>
  );
}

AttributionControl Component

Displays attribution text for map layers and data sources.

/**
 * Component for displaying attribution
 * @param props - Attribution control properties
 */
const AttributionControl: FunctionComponent<AttributionControlProps>;

type AttributionControlProps = Control.AttributionOptions;

Usage Example:

import { MapContainer, TileLayer, AttributionControl } from "react-leaflet";

function Map() {
  return (
    <MapContainer
      center={[51.505, -0.09]}
      zoom={13}
      attributionControl={false} // Disable default attribution
    >
      <TileLayer url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" />
      <AttributionControl position="bottomright" prefix="My Map" />
    </MapContainer>
  );
}

Types

Control.LayersOptions

// From Leaflet
namespace Control {
  interface LayersOptions extends ControlOptions {
    /** Whether control starts collapsed */
    collapsed?: boolean;
    /** Whether to auto expand control on hover */
    autoZIndex?: boolean;
    /** Whether to hide single base layer */
    hideSingleBase?: boolean;
    /** Whether to sort layers alphabetically */
    sortLayers?: boolean;
    /** Function to sort layers */
    sortFunction?: (
      layerA: Layer,
      layerB: Layer,
      nameA: string,
      nameB: string
    ) => number;
  }
}

Control.ZoomOptions

// From Leaflet
namespace Control {
  interface ZoomOptions extends ControlOptions {
    /** Text for zoom in button */
    zoomInText?: string;
    /** Title for zoom in button */
    zoomInTitle?: string;
    /** Text for zoom out button */
    zoomOutText?: string;
    /** Title for zoom out button */
    zoomOutTitle?: string;
  }
}

Control.ScaleOptions

// From Leaflet
namespace Control {
  interface ScaleOptions extends ControlOptions {
    /** Maximum width of the control in pixels */
    maxWidth?: number;
    /** Whether to show metric scale */
    metric?: boolean;
    /** Whether to show imperial scale */
    imperial?: boolean;
    /** Whether to update scale on move end (false = update continuously) */
    updateWhenIdle?: boolean;
  }
}

Control.AttributionOptions

// From Leaflet
namespace Control {
  interface AttributionOptions extends ControlOptions {
    /** Prefix text before attributions */
    prefix?: string | boolean;
  }
}

ControlOptions

// From Leaflet
interface ControlOptions {
  /** Position of the control */
  position?: ControlPosition;
}

type ControlPosition =
  | "topleft"
  | "topright"
  | "bottomleft"
  | "bottomright";

// Default z-index values for control positions:
// topleft/topright: z-index 1000
// bottomleft/bottomright: z-index 1000

Important Notes

  1. Control Positions: All controls accept a position prop with four options:

    • "topleft" (default for most)
    • "topright"
    • "bottomleft" (default for scale)
    • "bottomright"
  2. Default Controls: MapContainer includes zoom and attribution controls by default. Disable them to use custom ones:

    <MapContainer zoomControl={false} attributionControl={false}>
      <ZoomControl position="bottomright" />
      <AttributionControl position="topleft" />
    </MapContainer>
  3. LayersControl Structure: LayersControl requires specific structure:

    • Must contain LayersControl.BaseLayer and/or LayersControl.Overlay components
    • Each sub-component must have a name prop
    • Each sub-component must contain exactly one layer component
  4. Base Layer Behavior: Only one base layer can be active at a time (radio button behavior):

    <LayersControl.BaseLayer checked name="Default">
      {/* This layer starts active */}
    </LayersControl.BaseLayer>
  5. Overlay Behavior: Multiple overlays can be active simultaneously (checkbox behavior):

    <LayersControl.Overlay checked name="Markers">
      {/* Starts visible */}
    </LayersControl.Overlay>
    <LayersControl.Overlay name="Routes">
      {/* Starts hidden */}
    </LayersControl.Overlay>
  6. Collapsed Control: LayersControl can start collapsed:

    <LayersControl collapsed={true} position="topright">
  7. Scale Units: ScaleControl can show metric, imperial, or both:

    <ScaleControl metric={true} imperial={true} />
  8. Attribution Prefix: Customize or remove attribution prefix:

    <AttributionControl prefix="Powered by Leaflet" />
    <AttributionControl prefix={false} /> {/* No prefix */}
  9. Custom Control Text: Customize zoom button text:

    <ZoomControl
      zoomInText="+"
      zoomOutText="-"
      zoomInTitle="Zoom in"
      zoomOutTitle="Zoom out"
    />

Examples

Complete Control Setup

function MapWithControls() {
  return (
    <MapContainer
      center={[51.505, -0.09]}
      zoom={13}
      zoomControl={false}
      attributionControl={false}
    >
      <LayersControl position="topright">
        <LayersControl.BaseLayer checked name="Street Map">
          <TileLayer url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" />
        </LayersControl.BaseLayer>
        <LayersControl.BaseLayer name="Satellite">
          <TileLayer url="https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}" />
        </LayersControl.BaseLayer>
        <LayersControl.Overlay checked name="Markers">
          <LayerGroup>
            <Marker position={[51.505, -0.09]} />
            <Marker position={[51.51, -0.1]} />
          </LayerGroup>
        </LayersControl.Overlay>
      </LayersControl>

      <ZoomControl position="bottomright" />
      <ScaleControl position="bottomleft" />
      <AttributionControl position="bottomright" prefix={false} />
    </MapContainer>
  );
}

Dynamic Layer Control

function DynamicLayersControl({ layers }) {
  return (
    <MapContainer center={[51.505, -0.09]} zoom={13}>
      <LayersControl>
        {layers.baseLayers.map((layer) => (
          <LayersControl.BaseLayer
            key={layer.name}
            name={layer.name}
            checked={layer.default}
          >
            <TileLayer url={layer.url} attribution={layer.attribution} />
          </LayersControl.BaseLayer>
        ))}
        {layers.overlays.map((overlay) => (
          <LayersControl.Overlay
            key={overlay.name}
            name={overlay.name}
            checked={overlay.visible}
          >
            {overlay.component}
          </LayersControl.Overlay>
        ))}
      </LayersControl>
    </MapContainer>
  );
}

Custom Scale Display

function MapWithCustomScale() {
  return (
    <MapContainer center={[51.505, -0.09]} zoom={13}>
      <TileLayer url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" />
      <ScaleControl
        position="bottomleft"
        maxWidth={200}
        metric={true}
        imperial={false}
        updateWhenIdle={false}
      />
    </MapContainer>
  );
}

Grouped Overlays in LayersControl

function MapWithGroupedOverlays() {
  return (
    <MapContainer center={[51.505, -0.09]} zoom={13}>
      <LayersControl position="topright" collapsed={false}>
        <LayersControl.BaseLayer checked name="OpenStreetMap">
          <TileLayer url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" />
        </LayersControl.BaseLayer>

        <LayersControl.Overlay checked name="Cities">
          <FeatureGroup>
            <Marker position={[51.505, -0.09]}>
              <Popup>London</Popup>
            </Marker>
            <Marker position={[51.51, -0.1]}>
              <Popup>Westminster</Popup>
            </Marker>
          </FeatureGroup>
        </LayersControl.Overlay>

        <LayersControl.Overlay name="Parks">
          <FeatureGroup pathOptions={{ color: "green" }}>
            <Circle center={[51.505, -0.09]} radius={200} />
            <Circle center={[51.51, -0.1]} radius={150} />
          </FeatureGroup>
        </LayersControl.Overlay>

        <LayersControl.Overlay name="Routes">
          <Polyline
            positions={[[51.505, -0.09], [51.51, -0.1], [51.52, -0.11]]}
            pathOptions={{ color: "blue", weight: 3 }}
          />
        </LayersControl.Overlay>
      </LayersControl>

      <ZoomControl position="topleft" />
      <ScaleControl position="bottomleft" metric={true} imperial={true} />
    </MapContainer>
  );
}

Minimalist Control Setup

function MinimalMap() {
  return (
    <MapContainer
      center={[51.505, -0.09]}
      zoom={13}
      zoomControl={false}
      attributionControl={false}
      style={{ height: "100vh" }}
    >
      <TileLayer url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" />

      {/* Only zoom control, hidden by default */}
      <ZoomControl position="bottomright" />
    </MapContainer>
  );
}

Multi-Source Attribution

function MapWithMultiSourceAttribution() {
  return (
    <MapContainer center={[51.505, -0.09]} zoom={13} attributionControl={false}>
      <TileLayer
        url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
        attribution='Map data &copy; <a href="https://www.openstreetmap.org/">OpenStreetMap</a> contributors'
      />
      <AttributionControl
        position="bottomright"
        prefix='<a href="https://leafletjs.com/">Leaflet</a> | <a href="https://example.com">Custom Data</a>'
      />
    </MapContainer>
  );
}

Creating Custom Controls

Custom Info Control

import { createControlComponent } from "@react-leaflet/core";
import L from "leaflet";

const InfoControl = L.Control.extend({
  onAdd: function(map) {
    const container = L.DomUtil.create("div", "info-control");
    container.innerHTML = "<h4>Map Information</h4><p>Hover over features</p>";
    container.style.background = "white";
    container.style.padding = "10px";
    container.style.borderRadius = "5px";
    return container;
  },
});

const InfoControlComponent = createControlComponent((props) => new InfoControl(props));

// Usage
<MapContainer center={[51.505, -0.09]} zoom={13}>
  <TileLayer url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" />
  <InfoControlComponent position="topright" />
</MapContainer>

Interactive Custom Control

function CustomButtonControl() {
  const map = useMap();

  useEffect(() => {
    const CustomControl = L.Control.extend({
      onAdd: function() {
        const container = L.DomUtil.create("div", "leaflet-bar leaflet-control");
        const button = L.DomUtil.create("a", "custom-button", container);
        button.innerHTML = "🏠";
        button.href = "#";
        button.title = "Go Home";
        button.style.width = "30px";
        button.style.height = "30px";
        button.style.lineHeight = "30px";
        button.style.display = "block";
        button.style.textAlign = "center";

        L.DomEvent.on(button, "click", (e) => {
          L.DomEvent.stopPropagation(e);
          L.DomEvent.preventDefault(e);
          map.setView([51.505, -0.09], 13);
        });

        return container;
      },
    });

    const control = new CustomControl({ position: "topleft" });
    control.addTo(map);

    return () => {
      control.remove();
    };
  }, [map]);

  return null;
}

Legend Control

function LegendControl() {
  const map = useMap();

  useEffect(() => {
    const legend = L.control({ position: "bottomleft" });

    legend.onAdd = function() {
      const div = L.DomUtil.create("div", "legend");
      div.style.background = "white";
      div.style.padding = "10px";
      div.style.borderRadius = "5px";
      
      const grades = [0, 10, 20, 50, 100, 200, 500, 1000];
      const colors = ["#FFEDA0", "#FED976", "#FEB24C", "#FD8D3C", "#FC4E2A", "#E31A1C", "#BD0026", "#800026"];

      div.innerHTML = "<h4>Legend</h4>";
      for (let i = 0; i < grades.length; i++) {
        div.innerHTML +=
          '<i style="background:' + colors[i] + '; width: 18px; height: 18px; float: left; margin-right: 8px; opacity: 0.7;"></i> ' +
          grades[i] + (grades[i + 1] ? "&ndash;" + grades[i + 1] + "<br>" : "+");
      }

      return div;
    };

    legend.addTo(map);

    return () => {
      legend.remove();
    };
  }, [map]);

  return null;
}

Advanced Control Patterns

Dynamic Control Content

function DynamicInfoControl({ data }) {
  const map = useMap();
  const controlRef = useRef(null);

  useEffect(() => {
    if (!controlRef.current) {
      const InfoControl = L.Control.extend({
        onAdd: function() {
          const container = L.DomUtil.create("div", "info-control");
          container.style.background = "white";
          container.style.padding = "10px";
          container.style.borderRadius = "5px";
          controlRef.current = container;
          return container;
        },
      });

      const control = new InfoControl({ position: "topright" });
      control.addTo(map);
    }

    if (controlRef.current) {
      controlRef.current.innerHTML = `<h4>${data.title}</h4><p>${data.description}</p>`;
    }
  }, [map, data]);

  return null;
}

Search Control

function SearchControl({ onSearch }) {
  const map = useMap();

  useEffect(() => {
    const SearchControl = L.Control.extend({
      onAdd: function() {
        const container = L.DomUtil.create("div", "leaflet-bar search-control");
        const input = L.DomUtil.create("input", "", container);
        input.type = "text";
        input.placeholder = "Search...";
        input.style.padding = "5px";

        L.DomEvent.on(input, "keypress", (e) => {
          if (e.keyCode === 13) {
            onSearch(input.value);
          }
        });

        L.DomEvent.disableClickPropagation(container);
        L.DomEvent.disableScrollPropagation(container);

        return container;
      },
    });

    const control = new SearchControl({ position: "topleft" });
    control.addTo(map);

    return () => {
      control.remove();
    };
  }, [map, onSearch]);

  return null;
}

Troubleshooting

Control Not Appearing

Solutions:

  1. Verify control is added inside MapContainer
  2. Check position prop is valid
  3. Ensure control has visible content
  4. Check z-index and CSS styling

Control Position Conflicts

Solutions:

  1. Use different positions for different controls
  2. Adjust CSS margins to prevent overlap
  3. Create custom control positions
  4. Stack controls vertically in same position

Control Events Not Working

Solutions:

  1. Use L.DomEvent.disableClickPropagation()
  2. Use L.DomEvent.stopPropagation() in event handlers
  3. Prevent default behavior with L.DomEvent.preventDefault()
  4. Ensure event listeners are properly attached

Testing Controls

Unit Testing Controls

import { render } from '@testing-library/react';
import { MapContainer, ZoomControl } from 'react-leaflet';

test('renders zoom control', () => {
  const { container } = render(
    <MapContainer center={[51.505, -0.09]} zoom={13} zoomControl={false}>
      <ZoomControl position="bottomright" />
    </MapContainer>
  );
  
  expect(container).toBeInTheDocument();
});

Testing LayersControl

test('renders layers control with base layers', () => {
  render(
    <MapContainer center={[51.505, -0.09]} zoom={13}>
      <LayersControl position="topright">
        <LayersControl.BaseLayer checked name="OpenStreetMap">
          <TileLayer url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" />
        </LayersControl.BaseLayer>
      </LayersControl>
    </MapContainer>
  );
  
  // Add assertions
});

Accessibility for Controls

Keyboard Accessible Controls

<MapContainer 
  center={[51.505, -0.09]} 
  zoom={13}
  keyboard={true}
  zoomControl={false}
>
  <ZoomControl 
    position="topright"
    zoomInTitle="Zoom in (Keyboard: +)"
    zoomOutTitle="Zoom out (Keyboard: -)"
  />
</MapContainer>

ARIA Labels for Custom Controls

function AccessibleCustomControl() {
  const map = useMap();

  useEffect(() => {
    const CustomControl = L.Control.extend({
      onAdd: function() {
        const container = L.DomUtil.create("div", "custom-control");
        const button = L.DomUtil.create("button", "", container);
        button.innerHTML = "Reset View";
        button.setAttribute("aria-label", "Reset map to default view");
        button.setAttribute("role", "button");
        
        L.DomEvent.on(button, "click", () => {
          map.setView([51.505, -0.09], 13);
        });
        
        return container;
      },
    });

    const control = new CustomControl({ position: "topleft" });
    control.addTo(map);

    return () => {
      control.remove();
    };
  }, [map]);

  return null;
}