mgifford/accessibility-skills/maps

Claude Code and agent skill by mgifford/accessibility-skills

Trust tier 3
  • community
Source check pending

Checking that the SKILL.md file is still in the repository.

Load this skill whenever the project contains static or interactive maps, embedded map widgets (Google Maps, Leaflet, Mapbox, OpenStreetMap), or any geographic visualizations. Under no circumstances embed a map without a text-based alternative conveying the same information. Absolutely always ensure map controls are keyboard-accessible and that all meaningful map content is available without the visual map.

Install

npx vskill@latest install mgifford/accessibility-skills/maps

SKILL.md

Maps Accessibility Skill

Canonical source: examples/MAPS_ACCESSIBILITY_BEST_PRACTICES.md in mgifford/ACCESSIBILITY.md
This skill is derived from that file. When in doubt, the example is authoritative.

Apply these rules when implementing or reviewing any static or interactive map. Only load this skill if the project contains maps.


Core Mandate

No single text description works for every map. Start with the map's purpose and the tasks it supports, then provide accessible content and controls that preserve that purpose for people who cannot see, distinguish, point at, drag, zoom, or interpret the visual map. Keep the map and its structured alternative synchronized from the same data source.


Severity Scale (this skill)

LevelMeaning
CriticalEssential location data has no accessible alternative
SeriousInteractive map unreachable by keyboard; role="application" misused
ModerateSkip link missing; colour independence gap; popup focus not managed
MinorMissing prefers-reduced-motion for animations; legend incomplete

Classify the Map's Purpose First

Map purposeEssential taskCommon accessible presentation
Simple locatorWhere one place is and how to reach itConcise alt, address, landmark, directions, contact info
Location directoryFind and compare servicesSearchable list/table with names, addresses, distances, categories
Route/wayfindingTravel from A to BOrdered directions with distances, turns, landmarks, conditions
Thematic/quantitativeCompare values across areasSummary + data table with region names, values, units, period
Boundary/zoningUnderstand containment/adjacencyNamed boundary descriptions, affected locations, downloadable data
Indoor/multi-floorNavigate a buildingFloor selector, accessible route instructions, entrances/lifts/stairs
ExploratoryInvestigate spatial relationshipsSynchronized filters, structured results, accessible query tools

A map can serve more than one purpose — provide the alternatives needed for every essential task rather than assuming one short alt, table, or direction set is universally equivalent. WCAG 1.1.1's "equivalent purpose" does not mean reproducing every pixel or street label — it means preserving the information and tasks that matter in context. Research (the Map Equivalent-Purpose Framework) found tables and turn-by-turn directions can be insufficient when the purpose includes broad spatial understanding or exploration — treat this as guidance, not a separate WCAG criterion, but recognize when a table alone won't do.


Critical: Text Alternatives for Static Maps

html
<!-- Simple map: concise alt, details in HTML nearby -->
<figure>
<img src="clinic-location.png"
alt="The clinic is on the north side of King Street, immediately east of the Central Station entrance."
width="960" height="540">
<figcaption>
Northside Clinic, 200 King Street. The step-free entrance is on King Street.
<a href="#clinic-directions">Read accessible travel directions</a>.
</figcaption>
</figure>

Do not fill alt with turn-by-turn instructions or every road label — keep it concise and put detail nearby. For complex maps, provide both a short alternative AND a visible structured long description or clearly associated link:

html
<figure>
<img src="accessible-transit-routes.png"
alt="Map of step-free transit routes in the downtown area. Detailed route information follows."
width="1200" height="800">
<figcaption>
Step-free transit routes, updated 15 July 2026.
<a href="#transit-route-details">View route details and connections</a>.
</figcaption>
</figure>

Prefer visible structured content over aria-describedby for complex descriptions — AT generally flattens referenced headings/lists/tables into one string, losing navigable structure. alt="" is appropriate only when purely decorative or the same info is already fully available nearby — never call a meaningful or interactive map decorative to hide accessibility problems.


Critical: Structured Alternatives Matching the Task

Location list/table (for directories):

html
<table id="branch-table">
<caption>Library branches matching the current filters</caption>
<thead>
<tr><th scope="col">Branch</th><th scope="col">Address</th><th scope="col">Distance</th><th scope="col">Accessibility information</th></tr>
</thead>
<tbody>
<tr>
<th scope="row"><a href="/branches/central">Central Library</a></th>
<td>100 Main Street</td><td>600 metres</td>
<td>Step-free entrance, lift, accessible washroom, hearing loop</td>
</tr>
</tbody>
</table>

Explain how distance was calculated; include units; preserve filters/sort order; let users open the same details and perform the same actions from both the map and the list view.

Route directions:

html
<section id="clinic-directions" aria-labelledby="directions-heading">
<h2 id="directions-heading">Step-free route from Central Station</h2>
<p>Distance: 350 metres. Typical travel time: 6 to 10 minutes.</p>
<ol>
<li>Leave Central Station through the King Street step-free exit.</li>
<li>Turn left and continue east for approximately 250 metres.</li>
</ol>
<p>Route data checked 15 July 2026. Call 555-0100 to confirm temporary closures.</p>
</section>

Include as applicable: distances/travel time; street names/landmarks/turns; curb cuts/crossings/gradients/surfaces/steps; accessible entrances/lifts/ washrooms; known construction/closures; transit stop/platform info; an alternative route when the shortest isn't accessible. Don't rely only on cardinal directions — combine with street names, distance, and landmarks.

Thematic/quantitative maps: provide a plain-language summary, a data table with every value needed for comparison, region names/categories/units/ missing-data indicators, and the time period/source. Do not replace a choropleth map with a list of color names.

Legends are content, not decoration — put legend text in HTML, describe colors/symbols/patterns/line styles/sizes, keep changes synchronized with active layers, and never make the legend the only structured alternative.


Serious: Interactive Map Page Structure and Skip Link

html
<section aria-labelledby="service-map-heading">
<h2 id="service-map-heading">Find an accessible service location</h2>
<p><a href="#location-results-heading">Skip the interactive map and view locations</a></p>
<div class="map-interface">
<h3 id="map-view-heading">Interactive map</h3>
<p id="map-instructions">Use the named map controls to move or zoom.</p>
<div id="map-viewport" role="region" aria-labelledby="map-view-heading"
aria-describedby="map-instructions">
<!-- tested map library renders here -->
</div>
</div>
<section aria-labelledby="location-results-heading">
<h3 id="location-results-heading" tabindex="-1">Location results</h3>
<p id="location-result-status" role="status" aria-atomic="true"></p>
<ul id="location-results"><!-- same locations as map markers --></ul>
</section>
</section>

Missing skip link on an interactive map is Serious. The visible link to results is useful to screen reader, keyboard, switch, speech-input, mobile, low-bandwidth, and cognitively disabled users — don't reveal it only on keyboard focus.


Serious: Third-Party Map Embeds

html
<iframe src="https://maps.example.org/embed/service-locations"
title="Interactive map of service locations" loading="lazy"></iframe>
<p><a href="#service-location-list">View service locations as a list</a>
or <a href="/service-locations.csv">download the location data</a>.</p>

An <iframe> with no descriptive title is Serious. Confirm keyboard users can enter/operate/leave the embed; test controls, markers, popups, consent notices, and full-screen mode; keep the structured alternative outside the iframe, available if the provider fails or is blocked; confirm changes made in the embed are reflected in the site's alternative; retest after provider/plugin/config updates. Do not trust a provider's accessibility claim without testing the current rendered experience.

Map libraries: preserve a library's accessibility defaults unless testing supports a change (e.g., Leaflet's container/markers are keyboard operable by default — plugins can degrade that). Pin the version, review release notes, retest after updates — do not recommend a plugin as universally accessible or use unpinned @latest in production.


Serious: Keyboard Controls and role="application"

All map functionality must be keyboard-reachable unless the underlying movement is essential (WCAG's limited exception).

html
<div class="map-controls" role="group" aria-labelledby="map-controls-heading">
<h3 id="map-controls-heading">Map view controls</h3>
<button type="button">Zoom in</button>
<button type="button">Pan north</button>
<button type="button">Reset map view</button>
</div>

Users reach every control in logical order; native buttons work with Enter/ Space; focus stays visible and unobscured by floating controls/popups; users can move into and out of the map without a trap; rerendering the map doesn't discard focus or move it to <body>. Do not capture page-level arrow keys merely because a map is present — scope arrow-key panning to when the viewport has focus, and always allow Tab/Shift+Tab to leave. Single-character shortcuts must meet WCAG 2.1.4 (off/remap/scoped) with named buttons as an alternative.

Most maps do not need `role="application"`. It changes screen reader interaction modes and suppresses ordinary reading commands. Prefer native controls around the map, ordinary document content for results, a named region for the viewport, and documented scoped keyboard behavior only where the viewport needs it. Misusing it is Serious — only use application semantics when the team understands the AT consequences, has implemented a complete keyboard model, provides instructions and an exit path, and has tested supported combinations. Adding the role does not make a map accessible.

Large marker sets: hundreds of markers must not create hundreds of sequential Tab stops. Choose a tested model: one map-viewport Tab stop with documented directional navigation; roving tabindex within the current marker set; focusable markers only after narrowing results; or a structured location list as the primary keyboard interface. There is no universal WAI-ARIA map pattern — document the chosen behavior and test it.


Moderate: Markers, Clusters, and Popup Focus Management

html
<button type="button" class="map-marker" aria-label="City Hall, 123 Main Street"
aria-expanded="false" aria-controls="city-hall-details">
<svg aria-hidden="true" focusable="false"><!-- marker artwork --></svg>
</button>
<section id="city-hall-details" aria-labelledby="city-hall-heading" hidden>
<h3 id="city-hall-heading">City Hall</h3>
<p>123 Main Street. Step-free entrance on Queen Avenue.</p>
<a href="/locations/city-hall">City Hall details</a>
</section>

Update both hidden and aria-expanded together. Use ordinary non-modal content for marker popups — do not add `role="dialog"`/`aria-modal="true"` to every marker popup, only when the interaction is genuinely modal. If a popup has substantial content and focus moves into it, provide a close control and return focus to the triggering marker on close. Do not put "press Enter" instructions in a native button's name — the role already communicates operation.

Clusters need an accessible name, a count, and a keyboard action:

html
<button type="button" aria-expanded="false" aria-controls="downtown-location-results">
Show 7 locations in downtown
</button>

This is the start of the file. Read the full SKILL.md on GitHub.

Security

  • Security scanPassed, 95 of 100Pattern checks on the skill's files.
  • Model reviewPassed

Quality evaluation

  • Task evaluationNot runNo evaluation yet. Run one in Skill Studio.

Source

Popularity

7-day trend score
0

Not refreshed yet.

Works with

Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.

  • Claude Code
  • Cursor
  • GitHub Copilot
  • Windsurf
  • Codex
  • Gemini CLI
  • Cline
  • Amp
  • Roo Code
  • Goose
  • Continue
  • Kilo Code
  • Junie
  • Trae
  • OpenHands
  • Qwen Code
  • AI Agent
  • Tabnine Chat
  • Sourcegraph Cody
  • Amazon Q
  • Firebase Genkit
  • Sweep
  • Devin
  • Phind
  • Mentat
  • Claude Desktop
  • Aider
  • GPT Pilot
  • Plandex
  • bolt.new
  • v0
  • Replit Agent
  • Anse
  • TypingMind
  • BetterChatGPT
  • LibreChat
  • Warp AI
  • Pieces
  • Fig AI
Embed badge
vskill VERIFIED badge
[![vskill VERIFIED](https://verified-skill.com/api/v1/skills/mgifford/accessibility-skills/maps/badge)](https://verified-skill.com/skills/mgifford/accessibility-skills/maps)

Something wrong with this skill or its page? Tell us.