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.

Markers, clusters and the list view Layers showing the list of places in view as the primary browsing surface, synchronised with focusable named markers and cluster buttons on the map, and a status region for view changes. Markers, clusters and the list viewList of places in viewordered, named links — the primary way to browseMarkersbuttons named "Store Utrecht Centraal, open until 21:00"Clustersbuttons named "12 stores near Rotterdam, expand"Status"6 stores in view" after pans, zooms and filters
The list is the fastest way to browse; the map shows where — and the two follow each other.

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.”
Library-default markers versus accessible markers Comparison of default map markers that are images with click handlers against markers built as named buttons stepped with keys and synchronised with a list. Library-default markers versus accessible markers✗ Default markersImages or divs with click handlersNamed "Marker" or not at allClusters show a number only visuallyNo order; no list✓ Accessible markersButtons named with place and key factStepped nearest-first with Page keysSynchronised list of places in view
The accessible version is the same markers with a role, a name and a predictable order — plus the list.

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.

How should users browse these markers? Decision tree for marker browsing based on the number of markers in view and whether the question is spatial. How should users browse these markers?What is the user trying to do with the markers?Find or compare placesThe listsort by distance or nameAnswer a spatial questionMap steppingnearest-first with Page keysMany overlapping markersClustersnamed counts that expand bykeyboard
Lists win for browsing; the map wins for spatial questions — offer both, synchronised.

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.

Permalink to "Related"

← Back to Maps & Geospatial Data