Skip to main content
7 min read

Building AccessMap — A Lesson in Shipping Something That Matters

How I built a crowdsourced accessibility mapping app with Expo and Supabase — the technical decisions, the hard parts, and what actually shipped.

  • accessibility
  • expo
  • react-native
  • supabase

The problem nobody was solving

Accessibility infrastructure data is missing in most cities. Not wrong — missing. If you use a wheelchair, a walker, or a guide cane, you already know this: you can look up whether a restaurant takes reservations, but you can't reliably find out whether it has a step at the door.

The data that does exist is scattered. City governments sometimes publish ADA compliance records, but they're PDFs from 2009. Google Maps has wheelchair-accessible icons, but they're crowdsourced in ways users can't easily contribute to or dispute. The result is a gap that falls hardest on the people with the least margin for a surprise — someone who can't just "take the other entrance" when the main one turns out to have a broken ramp.

AccessMap is a mobile app that puts this data collection in users' hands. Anyone can drop a flag on the map — a broken ramp, missing tactile paving, an entrance with an unmarked step — and the community can verify or dispute it. Verified flags persist. Disputed ones get reviewed. The goal is a living, ground-truth layer on top of the map that people can actually trust before they leave the house.

Why Expo and Supabase

I could have built a web app. I didn't, because the use case is fundamentally in the field. You're at the top of a hill wondering if the pharmacy three blocks down has a step at the entrance. You need a phone in your pocket and a notification when someone near you flags something. A Progressive Web App could technically handle this, but push notifications, deep offline support, and native map performance are all significantly smoother in a real native client.

Expo lets me write React Native with one codebase and ship to both iOS and Android without maintaining two sets of native modules. That matters a lot for a solo project. The managed workflow handled 90% of the native complexity — camera access, location permissions, push tokens — without me touching Xcode or Android Studio configuration for most of the build.

Supabase was the natural backend choice for a few reasons. The Postgres row-level security model fits a user-contribution app well: `auth.uid()` in RLS policies means users can only mutate their own flags without any application-layer enforcement code. The realtime subscription layer — a Postgres logical replication stream surfaced through Supabase's WebSocket API — meant I could show new flags appearing on other users' maps instantly, without polling. And the Supabase client handles auth tokens, refresh cycles, and session persistence in a way that works reliably against React Native's AsyncStorage.

The thing that was actually hard

Making the interactive map work for screen reader users was the challenge I underestimated most.

The map component — rendered via `react-native-maps` — is a single native view from the screen reader's perspective. VoiceOver and TalkBack can't enumerate individual flag markers as discrete accessible elements unless you do explicit work to expose them. The naive implementation produces an app where sighted users get a rich, interactive map with colored markers and a heatmap overlay, and screen reader users get one unlabeled blob.

The solution: a parallel accessibility layer. An invisible `FlatList` positioned off-screen mirrors every visible flag as an accessible element with a proper label ("Broken ramp at Main St and 5th Ave, reported 3 days ago, verified by 2 people"). When a screen reader is active, this list is exposed and the map gets `accessibilityElementsHidden`. When it's off, the list is hidden. It's not perfect — the spatial relationship between flags is lost — but it gives screen reader users the actual data in navigable form rather than silence.

The EXIF privacy issue was a close second. When users attach photos to flags, the JPEG metadata can contain GPS coordinates precise enough to identify someone's home if they're reporting an inaccessible entrance nearby. Before any photo uploads to Supabase Storage, it goes through `expo-image-manipulator` to re-encode without metadata. Image quality is preserved; the coordinates are stripped. This runs on-device before the upload request fires, so the server never receives the EXIF data at all.

What shipped

The v1 build — currently in TestFlight — includes the full core loop.

Interactive map with clustering, color-coded flag markers (red for unverified, green for verified, gray for disputed), and a heatmap density overlay toggled from the toolbar.

Flag creation flow: drop a pin, choose a category, write a description, optionally attach a photo. Photos are EXIF-stripped on-device. Posted flags appear in realtime on other users' maps via Supabase subscriptions.

Verification and dispute on any flag — two verifications push it to green; a net negative dispute score moves it to gray for review.

Push notifications when a flag you submitted gets its first verification or reaches the verified threshold.

Offline-first flag browsing: flags for your current region are cached on-device. You can browse and read detail views without a connection; posting requires one.

WCAG 2.2 AA accessible UI throughout — the parallel screen-reader layer on the map, 44px minimum touch targets, sufficient color contrast on all flag states, and full keyboard navigation on iPad.

Dark mode: full system theme support, with the map style switching between light and satellite views.

What's next

TestFlight is live. App Store submission is the immediate next step — review is pending.

After that, the features beta testers have asked for most are search (find flags by address rather than navigating the map) and a proper dispute resolution flow (right now disputed flags sit in gray indefinitely; there should be a community review process). The data model already supports both — it's a UI problem.

Longer term: I want to export the flag dataset as open data — a public API that cities, accessibility advocates, or other apps could consume. That's the version of this project that has real scale impact. The app is the seed; the dataset is the thing that might matter.