Announcing Map Markers and Clusters
Permalink to "Announcing Map Markers and Clusters"Markers are the interactive content of most business maps: stores, vehicles, incidents, customers. Map libraries typically render them as images or <div>s that are clickable but not focusable, or focusable with names like “Marker” — and when many markers overlap, they merge into a cluster bubble showing a count that expands on click. For keyboard and screen reader users, the result is a map whose only content is unreachable.
This page makes markers and clusters reachable, named and announced, and pairs the map with a list that is often the better way to browse them. It belongs to maps & geospatial data.
Spec reference
Permalink to "Spec reference"- SC 2.1.1 Keyboard (A) — markers that can be clicked must be operable by keyboard.
- SC 4.1.2 Name, Role, Value (A) — a marker is a control (button or link) with a name; a cluster is a control with a name including its count and an expanded/collapsed state if it expands in place.
- SC 1.3.2 Meaningful Sequence and SC 2.4.3 Focus Order — the order of markers in the DOM should be predictable (for example by distance from the map centre, or alphabetical).
- SC 4.1.3 Status Messages (AA) — changes in what is in view (“6 stores in view”) are announced politely.
Leaflet markers can be given keyboard: true and alt/title options, making them focusable with names; MapLibre markers are HTML elements you create, so you control their role and name directly.
When to rely on the list — and when on the map
Permalink to "When to rely on the list — and when on the map"Rely on the list for browsing and comparing: it can be sorted by distance or name, it reads linearly, and it works with every assistive technology. Show it beside the map on wide screens and as a tab or toggle on narrow ones.
Rely on the map for spatial questions — “which store is closest to this junction”, “which vehicles are north of the river” — and make markers reachable for those, with keyboard stepping in a predictable order.
The misapplication to name is making each marker a separate Tab stop across the whole map. With hundreds of markers, the map becomes a keyboard trap in practice. Keep the map as one tab stop and step through markers with other keys, or rely on the list.
Annotated code example
Permalink to "Annotated code example"// Markers as named buttons, stepped with Page keys inside the focused map
function makeMarker(store) {
const el = document.createElement('button');
el.type = 'button';
el.className = 'map-marker';
el.tabIndex = -1; // not a Tab stop: map is one stop
// SC 4.1.2: place and key fact in the name
el.setAttribute('aria-label', `${store.name}, ${store.openText}`);
el.addEventListener('click', () => openPopup(store, el));
return new maplibregl.Marker({ element: el }).setLngLat(store.coords).addTo(map);
}
function makeCluster(cluster) {
const el = document.createElement('button');
el.type = 'button';
el.className = 'map-cluster';
el.tabIndex = -1;
// SC 4.1.2: count, area and action
el.setAttribute('aria-label', `${cluster.count} stores near ${cluster.placeName}, expand`);
el.addEventListener('click', () => {
map.flyTo({ center: cluster.center, zoom: cluster.expansionZoom });
status(`Zoomed in: ${cluster.count} stores near ${cluster.placeName}.`);
});
return el;
}
// Step through items in view in a stable order: nearest to the centre first
function stepFeature(dir) {
const items = itemsInView().sort((a, b) => dist(a, map.getCenter()) - dist(b, map.getCenter()));
current = (current + dir + items.length) % items.length;
const el = items[current].element;
el.focus(); // announced by its aria-label
status(`${current + 1} of ${items.length} in view.`);
}
<!-- The synchronised list: primary browsing surface -->
<section aria-labelledby="list-h" class="map-list">
<h3 id="list-h">Stores in view <span id="list-count">(6)</span></h3>
<ol>
<li><a href="/stores/utrecht-centraal" data-store="utrecht-centraal">Utrecht Centraal — 0.4 km, open until 21:00</a></li>
…
</ol>
</section>
Focusing a list item highlights its marker on the map, and activating a marker scrolls the list to its item. Keeping the two in sync lets sighted keyboard users use either and lets screen reader users rely on the list without losing the map’s context.
Keyboard & AT behaviour
Permalink to "Keyboard & AT behaviour"| Event | Expected behaviour | Announcement |
|---|---|---|
Page Down in the map |
Focus the nearest marker | “Store Utrecht Centraal, open until 21:00, button” then “1 of 6 in view.” |
Page Down again |
Next marker by distance | “Store Utrecht Oost, open until 20:00, button” … |
Page Down onto a cluster |
Focus the cluster | “12 stores near Rotterdam, expand, button” |
Enter on cluster |
Zoom in to expand | “Zoomed in: 12 stores near Rotterdam.” then view description |
Enter on marker |
Popup opens next to it | Popup content; Escape closes and returns focus |
| Filter “Open now” | Markers and list update | “4 stores in view.” |
Integration context
Permalink to "Integration context"The view description after pans and zooms comes from keyboard panning and zooming for interactive maps; marker stepping extends the same focused-map key handler. Ordering unordered points for keyboard stepping is the same problem as in keyboard navigation for scatter plots, where x order plays the role distance plays here.
Marker popups follow the tooltip rules — dismissible, persistent, not covering the marker — from accessible tooltips for chart data points; popups with links or actions should be small dialogs that take focus and return it on close.
Gotchas
Permalink to "Gotchas"Marker names from icons. A marker whose only content is an icon image with alt="marker" is announced as “marker, button”. Name it with the place.
Clusters that re-form. Zooming re-clusters markers, destroying the focused element. After a zoom, move focus to the map container or the nearest resulting marker.
Live markers. Vehicle positions that move every second should not re-announce; treat them like a live feed and summarise on demand.
Design system notes
Permalink to "Design system notes"Provide marker and cluster factories that produce named buttons with the right tab behaviour, a list component that syncs with the map through shared ids, and a stepping helper that orders by distance. Teams then supply data and the name formatter; the component guarantees the pattern.
Testing checklist
Permalink to "Testing checklist"FAQ
Permalink to "FAQ"How should map markers be named for screen readers?
With the place and its most useful fact — “Store Utrecht Centraal, open until 21:00” — on a button element. Generic names like “marker” or icon file names give users nothing to act on.
Should every map marker be a Tab stop?
No. Keep the map as one Tab stop and let users step between markers with keys such as Page Up and Page Down, or browse them in a synchronised list.
How should marker clusters be announced?
As buttons whose names include the count, the area and the action — “12 stores near Rotterdam, expand” — and announce the result when they expand.
Why provide a list next to the map?
Because lists are the fastest, most reliable way to browse places with a keyboard or screen reader. Keep it in sync with the map so both show the same places.
Related
Permalink to "Related"- Keyboard panning and zooming — moving the view
- Keyboard navigation for scatter plots — ordering unordered points
- Accessible tooltips for data points — marker popups