Accessible Map Legends and Layer Toggles
Permalink to "Accessible Map Legends and Layer Toggles"Maps encode meaning in colour bands, symbols and overlapping layers: flood zones in blue, schools as circles, a choropleth of income by district. The legend explains the encoding and the layer panel controls what is visible. Both are often rendered inside a canvas or as tiny icon toggles without names. Screen reader users cannot read the legend; keyboard users cannot reach the toggles; colour-blind users cannot match swatches to areas. Fixing the legend and layer controls is one of the highest-value accessibility improvements for map interfaces.
This guide belongs to accessible maps and geospatial data.
Spec reference
Permalink to "Spec reference"- SC 1.1.1 (A) — legend content available as text.
- SC 1.4.1 Use of Color (A) — categories distinguishable without colour (patterns, symbols, labels).
- SC 1.4.11 Non-text Contrast (AA) — legend swatches and map areas meet 3:1 where needed to understand them.
- SC 4.1.2 (A) — layer toggles named with state.
- SC 4.1.3 (AA) — layer changes announced.
When to use — and when not to
Permalink to "When to use — and when not to"Use HTML legends and layer controls for every interactive map. For static map images, place the legend as text below the image and provide a table or summary of the data.
Do not bake the legend into the map tiles or canvas. The misapplication: a legend drawn on the canvas in the corner, invisible to screen readers and impossible to enlarge without zooming the whole map.
Annotated code example
Permalink to "Annotated code example"<aside aria-labelledby="layers-h" class="map-panel">
<h2 id="layers-h">Map layers</h2>
<fieldset>
<legend class="sr-only">Visible layers</legend>
<label><input type="checkbox" checked data-layer="flood"> Flood zones</label>
<label><input type="checkbox" checked data-layer="schools"> Schools (42 in view)</label>
<label><input type="checkbox" data-layer="transit"> Transit lines</label>
</fieldset>
<h3 id="legend-h">Legend</h3>
<ul aria-labelledby="legend-h" class="legend">
<li><svg viewBox="0 0 20 20" aria-hidden="true">…hatched…</svg> High flood risk (hatched)</li>
<li><svg viewBox="0 0 20 20" aria-hidden="true">…dotted…</svg> Medium flood risk (dotted)</li>
<li><svg viewBox="0 0 20 20" aria-hidden="true">…circle…</svg> School</li>
</ul>
</aside>
<p role="status" id="map-status"></p>
panel.addEventListener('change', e => {
const layer = e.target.dataset.layer;
map.setLayerVisible(layer, e.target.checked);
renderLegend(map.visibleLayers()); // legend follows layers
status.textContent = `${labelFor(layer)} ${e.target.checked ? 'shown' : 'hidden'}. ${summary(layer)}`;
});
Keyboard & AT behaviour
Permalink to "Keyboard & AT behaviour"| Action | Announcement |
|---|---|
| Tab to a layer checkbox | “Schools (42 in view), checkbox, checked” |
| Space to hide | “Schools hidden.” |
| Space to show Flood zones | “Flood zones shown. 3 high-risk areas in view.” |
| Navigate legend list | “High flood risk (hatched)”, list item |
| Zoom changes visible features | Counts in labels update; no announcement |
Step-by-step implementation
Permalink to "Step-by-step implementation"- Move the legend out of the map. Render it as a list in the side panel or below the map, using inline SVG swatches hidden from assistive technology and text describing each category.
- Add non-colour cues. Hatching, dots or symbol shapes for categories; mention the cue in the legend text so users can match it.
- Build toggles as checkboxes. Native checkboxes in a fieldset with clear labels. Include counts in view when useful.
- Sync legend and layers. When a layer is hidden, remove its legend entries or mark them hidden, so the legend describes what is visible.
- Announce changes. Say what was shown or hidden and, if helpful, a one-line summary of what the layer adds in the current view.
Integration context
Permalink to "Integration context"Data alternatives for maps are covered in table alternatives for choropleth maps. Marker announcements are in announcing map markers and clusters, and map navigation in keyboard panning and zooming for maps. Colour choices follow accessible sequential and diverging palettes and patterns patterns and shapes for colour-independent charts.
Gotchas
Permalink to "Gotchas"Map library controls. Built-in layer controls in map libraries are often icon buttons with no names; replace them or patch names.
Legend overlaid on the map. If the legend floats over the map, it must not obscure focused markers and should be collapsible.
Continuous scales. For gradients, list breakpoints as text (“0–10%, 10–25%…”) rather than only showing a colour ramp.
Edge cases
Permalink to "Edge cases"- Many layers. Group layers by theme with nested fieldsets; offer “Show all” and “Hide all”.
- Mutually exclusive base maps. Use radio buttons for base map choice, not checkboxes.
- Scale-dependent layers. If a layer only appears at certain zooms, say so in its label: “Building outlines (zoom in to see)”.
Worked example
Permalink to "Worked example"A city planning portal drew its flood-risk legend into the map canvas and used small icon toggles for layers. Screen reader users could not tell what the colours meant or turn layers on, and colour-blind planners confused medium and high risk. The team moved the legend to an HTML list with hatched and dotted patterns, replaced toggles with labelled checkboxes and announced layer changes with a short summary. Planners with colour vision deficiency could read risk bands, and screen reader users could explore layers.
Common mistakes
Permalink to "Common mistakes"- Legends drawn into the map.
- Colour-only categories.
- Unnamed icon toggles.
- Legend not updated when layers change.
Testing checklist
Permalink to "Testing checklist"Testing it with a screen reader
Permalink to "Testing it with a screen reader"With NVDA, navigate to the “Map layers” heading and tab through the checkboxes, confirming names and states. Toggle a layer and listen for the change message. Arrow through the legend list and check each entry describes its pattern or shape. Then view the map in a greyscale filter and confirm categories remain distinguishable. In VoiceOver, repeat the toggle test and confirm the fieldset grouping is announced.
Legends for printed and exported maps
Permalink to "Legends for printed and exported maps"Maps are often exported as images or PDFs for reports. Make sure exports include the legend as real text, positioned beside the map rather than overlaid, with the same patterns. Add a caption that names the layers shown and the date of the data. If the export is an image in a document, its alternative text should summarise what the map shows, and the document should include the legend text and a data table nearby.
Key points
Permalink to "Key points"- The legend is HTML text beside the map, never pixels inside it.
- Every colour category also has a pattern or shape, and the legend text names it.
- Layer controls are native checkboxes or radios with clear labels and counts.
- Turning a layer on or off is announced once, with a short summary of what changed in view.
Design system notes
Permalink to "Design system notes"Provide map panel components for layers and legends that read from layer definitions, including pattern and symbol metadata, so legends cannot drift from the map.
FAQ
Permalink to "FAQ"Can the legend be inside the map container?
Yes, as long as it is HTML, readable and not obscuring content.
Should legend swatches have alt text?
Hide swatches and describe the encoding in the text.
Checkbox or switch for layers?
Checkboxes are familiar; switches work if named and stateful.
How do I describe gradients?
List the value ranges as text.
Should layer changes move focus?
No; keep focus on the toggle.
What about clustering?
Describe cluster symbols in the legend too.
Related
Permalink to "Related"- Table alternatives for choropleth maps — data alternatives
- Search results lists synced with maps — list views
- Patterns and shapes for colour-independent charts — non-colour encoding