Add a custom legend
Stay organized with collections
Save and categorize content based on your preferences.
Page Summary
-
This tutorial demonstrates how to create and add a legend to a Google Map using custom controls.
-
The legend provides information about custom markers, such as parking, libraries, and info points, displayed on the map.
-
It leverages HTML for structure, CSS for styling, and JavaScript/TypeScript for dynamic content and map interaction.
-
You can customize the legend's appearance (font, background, border) and position (e.g., right-bottom) as needed.
-
The legend content is dynamically generated based on the defined marker styles, ensuring it reflects the map's markers.
Overview
Learn how to create a legend on a Google map through this tutorial. Legends generally describe the symbols and markers on a map. You can create them using the positioning feature of custom controls.
The map below contains a legend that provides information about three different custom markers on the map.
The sections below explain the main steps to create a legend.
See the Complete code example section for the full code.
Create the legend container
Create the legend container by adding a div to the gmp-map element.
Use control slotting to set the position of the legend. Read more about
control position options.
<gmp-map center="-33.91722,151.23064" zoom="16.5" map-id="DEMO_MAP_ID">
<div id="legend" slot="control-inline-start-block-start"></div>
</gmp-map>
Apply styles to set the visual appearance of the legend. This example styles the legend container, title, and item.
#legend{
background-color:#fff;
width:15em;
margin:1em;
border-radius:8px;
font-family:Roboto,Arial,sans-serif;
overflow:hidden;
box-shadow:02px6pxrgba(0,0,0,0.3);
}
.legend-item{
margin:0.5em1em;
display:flex;
flex-direction:row;
align-items:center;
padding-bottom:0.5em;
}
.legend-icon{
margin-right:1em;
font-size:24px;
}
.legend-text{
font-size:1.5em;
}
Add the legend content
The makeLegend() function programmatically builds a visual map legend and
inserts it into the page's DOM. Here is how it works:
- Imports the Library: It dynamically imports the
PinElementclass from the Maps SDK (await google.maps.importLibrary('marker')).
const{PinElement}=awaitgoogle.maps.importLibrary('marker');
- Prepares the Container: It grabs the existing empty
<div id="legend">from the HTML and prepends a "Legend" title element to it.
constlegend=document.getElementById('legend')!;
consttitle=document.createElement('div');
title.innerText='Legend';
title.classList.add('title');
legend.appendChild(title);
- Iterates Over the Configurations: It loops through the
iconsconfiguration object to generate a row for each item (Parking, Library, Info). The helper function looks like this:
asyncfunctionmakeLegend(){
const{PinElement}=awaitgoogle.maps.importLibrary('marker');
constlegend=document.getElementById('legend');
consttitle=document.createElement('div');
title.innerText='Legend';
title.classList.add('title');
legend.appendChild(title);
for(consttypeofObject.values(icons)){
constwrapper=document.createElement('div');
wrapper.classList.add('legend-item');
constpin=newPinElement({
background:type.color,
borderColor:type.color,
glyphSrc:newURL(
`data:image/svg+xml;charset=UTF-8,${encodeURIComponent(type.icon)}`
),
scale:1.0,
});
// PinElement extends HTMLElement directly, so we can append it directly!
constpinContainer=document.createElement('div');
pinContainer.classList.add('legend-icon');
pinContainer.appendChild(pin);
consttxt=document.createElement('div');
txt.classList.add('legend-text');
txt.innerText=type.name;
wrapper.appendChild(pinContainer);
wrapper.appendChild(txt);
legend.appendChild(wrapper);
}
}
See Style point data features for another example of a programmatically generated legend.
Complete code example
The section below displays the entire code you need to create the map and legend in this tutorial.
TypeScript
constparkingSvg=`<svg fill="white" xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path d="M0 0h24v24H0z" fill="none"/><path d="M13 3H6v18h4v-6h3c3.31 0 6-2.69 6-6s-2.69-6-6-6zm.2 8H10V7h3.2c1.1 0 2 .9 2 2s-.9 2-2 2z"/></svg>`; constlibrarySvg=`<svg fill="white" xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path d="M0 0h24v24H0z" fill="none"/><path d="M12 11.55C9.64 9.35 6.48 8 3 8v11c3.48 0 6.64 1.35 9 3.55 2.36-2.19 5.52-3.55 9-3.55V8c-3.48 0-6.64 1.35-9 3.55zM12 8c1.66 0 3-1.34 3-3s-1.34-3-3-3-3 1.34-3 3 1.34 3 3 3z"/></svg>`; constinfoSvg=`<svg fill="white" xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path d="M0 0h24v24H0z" fill="none"/><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z"/></svg>`; consticons:Record<string,{name:string;icon:string;color:string}>={ parking:{ name:'Parking', icon:parkingSvg, color:'#1E88E5',// Blue }, library:{ name:'Library', icon:librarySvg, color:'#43A047',// Green }, info:{ name:'Info', icon:infoSvg, color:'#E53935',// Red }, }; asyncfunctioninit():Promise<void>{ const[{AdvancedMarkerElement,PinElement},{event}]= awaitPromise.all([ google.maps.importLibrary('marker'), google.maps.importLibrary('core'), google.maps.importLibrary('maps'), ]); constmapElement=document.querySelector('gmp-map')!; constinnerMap=mapElement.innerMap; // Wait for the map to load before building the legend. event.addListenerOnce(innerMap,'idle',()=>{ voidmakeLegend(); }); constfeatures=[ { position:{lat:-33.91721,lng:151.2263}, type:'info', }, { position:{lat:-33.91539,lng:151.2282}, type:'info', }, { position:{lat:-33.91747,lng:151.22912}, type:'info', }, { position:{lat:-33.9191,lng:151.22907}, type:'info', }, { position:{lat:-33.91725,lng:151.23011}, type:'info', }, { position:{lat:-33.91872,lng:151.23089}, type:'info', }, { position:{lat:-33.91784,lng:151.23094}, type:'info', }, { position:{lat:-33.91682,lng:151.23149}, type:'info', }, { position:{lat:-33.9179,lng:151.23463}, type:'info', }, { position:{lat:-33.91666,lng:151.23468}, type:'info', }, { position:{lat:-33.916988,lng:151.23364}, type:'info', }, { position:{lat:-33.91662347903106,lng:151.22879464019775}, type:'parking', }, { position:{lat:-33.916365282092855,lng:151.22937399734496}, type:'parking', }, { position:{lat:-33.91665018901448,lng:151.2282474695587}, type:'parking', }, { position:{lat:-33.919543720969806,lng:151.23112279762267}, type:'parking', }, { position:{lat:-33.91608037421864,lng:151.23288232673644}, type:'parking', }, { position:{lat:-33.91851096391805,lng:151.2344058214569}, type:'parking', }, { position:{lat:-33.91818154739766,lng:151.2346203981781}, type:'parking', }, { position:{lat:-33.91727341958453,lng:151.23348314155578}, type:'library', }, ]; for(constfeatureoffeatures){ consticonData=icons[feature.type]; constpin=newPinElement({ background:iconData.color, borderColor:iconData.color, glyphSrc:newURL( `data:image/svg+xml;charset=UTF-8,${encodeURIComponent(iconData.icon)}` ), scale:1.5, }); constmarker=newAdvancedMarkerElement({ position:feature.position, title:iconData.name, }); marker.append(pin); mapElement.append(marker); } } asyncfunctionmakeLegend(){ const{PinElement}=awaitgoogle.maps.importLibrary('marker'); constlegend=document.getElementById('legend')!; consttitle=document.createElement('div'); title.innerText='Legend'; title.classList.add('title'); legend.appendChild(title); for(consttypeofObject.values(icons)){ constwrapper=document.createElement('div'); wrapper.classList.add('legend-item'); constpin=newPinElement({ background:type.color, borderColor:type.color, glyphSrc:newURL( `data:image/svg+xml;charset=UTF-8,${encodeURIComponent(type.icon)}` ), scale:1.0, }); // PinElement extends HTMLElement directly, so we can append it directly! constpinContainer=document.createElement('div'); pinContainer.classList.add('legend-icon'); pinContainer.appendChild(pin); consttxt=document.createElement('div'); txt.classList.add('legend-text'); txt.innerText=type.name; wrapper.appendChild(pinContainer); wrapper.appendChild(txt); legend.appendChild(wrapper); } } voidinit();
JavaScript
constparkingSvg=`<svg fill="white" xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path d="M0 0h24v24H0z" fill="none"/><path d="M13 3H6v18h4v-6h3c3.31 0 6-2.69 6-6s-2.69-6-6-6zm.2 8H10V7h3.2c1.1 0 2 .9 2 2s-.9 2-2 2z"/></svg>`; constlibrarySvg=`<svg fill="white" xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path d="M0 0h24v24H0z" fill="none"/><path d="M12 11.55C9.64 9.35 6.48 8 3 8v11c3.48 0 6.64 1.35 9 3.55 2.36-2.19 5.52-3.55 9-3.55V8c-3.48 0-6.64 1.35-9 3.55zM12 8c1.66 0 3-1.34 3-3s-1.34-3-3-3-3 1.34-3 3 1.34 3 3 3z"/></svg>`; constinfoSvg=`<svg fill="white" xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path d="M0 0h24v24H0z" fill="none"/><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z"/></svg>`; consticons={ parking:{ name:'Parking', icon:parkingSvg, color:'#1E88E5',// Blue }, library:{ name:'Library', icon:librarySvg, color:'#43A047',// Green }, info:{ name:'Info', icon:infoSvg, color:'#E53935',// Red }, }; asyncfunctioninit(){ const[{AdvancedMarkerElement,PinElement},{event}]= awaitPromise.all([ google.maps.importLibrary('marker'), google.maps.importLibrary('core'), google.maps.importLibrary('maps'), ]); constmapElement=document.querySelector('gmp-map'); constinnerMap=mapElement.innerMap; // Wait for the map to load before building the legend. event.addListenerOnce(innerMap,'idle',()=>{ voidmakeLegend(); }); constfeatures=[ { position:{lat:-33.91721,lng:151.2263}, type:'info', }, { position:{lat:-33.91539,lng:151.2282}, type:'info', }, { position:{lat:-33.91747,lng:151.22912}, type:'info', }, { position:{lat:-33.9191,lng:151.22907}, type:'info', }, { position:{lat:-33.91725,lng:151.23011}, type:'info', }, { position:{lat:-33.91872,lng:151.23089}, type:'info', }, { position:{lat:-33.91784,lng:151.23094}, type:'info', }, { position:{lat:-33.91682,lng:151.23149}, type:'info', }, { position:{lat:-33.9179,lng:151.23463}, type:'info', }, { position:{lat:-33.91666,lng:151.23468}, type:'info', }, { position:{lat:-33.916988,lng:151.23364}, type:'info', }, { position:{lat:-33.91662347903106,lng:151.22879464019775}, type:'parking', }, { position:{lat:-33.916365282092855,lng:151.22937399734496}, type:'parking', }, { position:{lat:-33.91665018901448,lng:151.2282474695587}, type:'parking', }, { position:{lat:-33.919543720969806,lng:151.23112279762267}, type:'parking', }, { position:{lat:-33.91608037421864,lng:151.23288232673644}, type:'parking', }, { position:{lat:-33.91851096391805,lng:151.2344058214569}, type:'parking', }, { position:{lat:-33.91818154739766,lng:151.2346203981781}, type:'parking', }, { position:{lat:-33.91727341958453,lng:151.23348314155578}, type:'library', }, ]; for(constfeatureoffeatures){ consticonData=icons[feature.type]; constpin=newPinElement({ background:iconData.color, borderColor:iconData.color, glyphSrc:newURL( `data:image/svg+xml;charset=UTF-8,${encodeURIComponent(iconData.icon)}` ), scale:1.5, }); constmarker=newAdvancedMarkerElement({ position:feature.position, title:iconData.name, }); marker.append(pin); mapElement.append(marker); } } asyncfunctionmakeLegend(){ const{PinElement}=awaitgoogle.maps.importLibrary('marker'); constlegend=document.getElementById('legend'); consttitle=document.createElement('div'); title.innerText='Legend'; title.classList.add('title'); legend.appendChild(title); for(consttypeofObject.values(icons)){ constwrapper=document.createElement('div'); wrapper.classList.add('legend-item'); constpin=newPinElement({ background:type.color, borderColor:type.color, glyphSrc:newURL( `data:image/svg+xml;charset=UTF-8,${encodeURIComponent(type.icon)}` ), scale:1.0, }); // PinElement extends HTMLElement directly, so we can append it directly! constpinContainer=document.createElement('div'); pinContainer.classList.add('legend-icon'); pinContainer.appendChild(pin); consttxt=document.createElement('div'); txt.classList.add('legend-text'); txt.innerText=type.name; wrapper.appendChild(pinContainer); wrapper.appendChild(txt); legend.appendChild(wrapper); } } voidinit();
CSS
html, body{ height:100%; margin:0; padding:0; } #legend{ background-color:#fff; width:15em; margin:1em; border-radius:8px; font-family:Roboto,Arial,sans-serif; overflow:hidden; box-shadow:02px6pxrgba(0,0,0,0.3); } .title{ padding:0.5em1em; font-weight:bold; font-size:1.5em; margin-bottom:0.5em; background-color:rgb(66,133,244); color:white; box-sizing:border-box; } .legend-item{ margin:0.5em1em; display:flex; flex-direction:row; align-items:center; padding-bottom:0.5em; } .legend-icon{ margin-right:1em; font-size:24px; } .legend-text{ font-size:1.5em; }
HTML
<html>
<head>
<title>Custom Legend</title>
<link rel="stylesheet" type="text/css" href="./style.css" />
<script type="module" src="./index.js"></script>
<script>
// prettier-ignore
(g=>{var h,a,k,p="The Google Maps JavaScript API",c="google",l="importLibrary",q="__ib__",m=document,b=window;b=b[c]||(b[c]={});var d=b.maps||(b.maps={}),r=new Set,e=new URLSearchParams,u=()=>h||(h=new Promise(async(f,n)=>{await (a=m.createElement("script"));e.set("libraries",[...r]+"");for(k in g)e.set(k.replace(/[A-Z]/g,t=>"_"+t[0].toLowerCase()),g[k]);e.set("callback",c+".maps."+q);a.src=`https://maps.${c}apis.com/maps/api/js?`+e;d[q]=f;a.onerror=()=>h=n(Error(p+" could not load."));a.nonce=m.querySelector("script[nonce]")?.nonce||"";m.head.append(a)}));d[l]?console.warn(p+" only loads once. Ignoring:",g):d[l]=(f,...n)=>r.add(f)&&u().then(()=>d[l](f,...n))})({
key: "GOOGLE_MAPS_API_KEY"
});
</script>
</head>
<body>
<gmp-map center="-33.91722,151.23064" zoom="16.5" map-id="DEMO_MAP_ID">
<div id="legend" slot="control-inline-start-block-start"></div>
</gmp-map>
</body>
</html>