Inspiration
Slack is where work happens โ and for hundreds of millions of people, parts of it are inaccessible by default. Images get pasted into channels with no descriptions. Threads get long, jargon-dense, hostile to anyone navigating with a screen reader, dyslexia, or English as a second language. The accessible choice exists, but it's always extra work โ and "extra work" loses to "send the message" every time.
We wanted to flip the default. Instead of asking people to remember to write alt text or simplify their language, what if Slack itself could do it โ one click, right where the message already lives?
What it does
AccessMate is a Slack agent with three primitives:
๐ผ๏ธ One-click alt text. Right-click any image in Slack โ Generate alt text โ AccessMate returns a screen-reader-ready description in seconds. Also available via /accessmate alt for keyboard users.
๐งต One-click thread simplification. Right-click any noisy thread โ Simplify thread โ AccessMate distills the conversation into plain language at your chosen reading level. Same surface via /accessmate simplify.
๐ฆป On-demand workspace audit. /accessmate digest scans every channel AccessMate can see, finds the day's images that shipped without alt text, and DMs you the list โ each one linked back to the source. Proactive accessibility, on demand.
Plus an App Home tab for status and an @AccessMate mention handler that maintains thread context for multi-turn questions.
How we built it
- Slack Bolt JS in CommonJS, running in Socket Mode โ the entire app boots from
node app.jswith zero infra. - Gemini 3.1 Flash-Lite via the OpenAI-compatible endpoint. Keeps the
openaiSDK, swaps the base URL. Vision-capable, fast, cheap enough to use liberally. - Single LLM seam in
lib/llm.js. Every model call goes through one function (chatComplete) with one error classifier. Provider swaps are a one-file change. - Single error seam in
lib/errorCard.js. Every failure path renders the same Block Kit card with a screen-reader-friendlytext:fallback. Consistent error UX by construction, not discipline. - Retry + rate-limit-aware Slack HTTP wrapper (
lib/slackHttp.js). The digest audit walks 100+ channels and survives Slack's pagination cliffs. - JSON-backed conversation store so
@mentionQ&A carries context across multi-turn threads.
Challenges we ran into
Making errors accessible. Block Kit's text: fallback is what screen readers actually announce. Early error states had rich blocks but empty text fields โ invisible to the people the app exists to serve. Refactored every error path through errorCard.js so a meaningful fallback is always set.
Resisting AI hallucinations on alt text. Early prompts would produce confident-sounding descriptions even when the image was unreadable. Tightened the prompt so Gemini flags low-confidence cases ("image too dark to describe in detail") instead of inventing detail. Honesty over impressiveness.
The digest's "who am I DM-ing" problem. The original cron sent the digest to a single OWNER_USER_ID env var โ a single-user signal that broke on Day 1 of multi-user use. Switched to on-demand via slash command, passing command.user_id through. Honest about the v1 scope, opt-in by default.
Accomplishments that we're proud of
- Three Slack-native primitives that each do one thing well, all from surfaces people already touch (right-click menu, slash command, mention, App Home).
- Screen-reader parity by construction. Every error card, every status response has a meaningful
text:fallback. Eating our own dogfood. - Single LLM seam clean enough to fork into a sister product (GreenLog) with the prompts and identity swapped.
- Zero infra to demo. Socket Mode means the entire app runs from one
node app.jscommand โ no webhooks, no ngrok, no OAuth server.
What we learned
- Accessibility isn't a feature, it's a default. The moment AccessMate makes alt text the path of least resistance, people generate it. Friction was the bug all along.
- For Good apps must dogfood. A Slack agent that ships inaccessible Block Kit cards contradicts its own thesis. Every surface gets screen-reader-tested.
- Confident refusal beats confident invention. When the model can't describe an image well, saying so is more useful than producing plausible-sounding wrong text.
What's next for AccessMate
- One-tap fix from the digest. Each digest item gets a button that opens the alt-text generator inline, instead of requiring a permalink jump + right-click.
- Per-user subscribe to the cron. Bring back the morning digest as an opt-in subscription managed from the App Home, replacing the env-var pinning that v1 dropped.
- Auto-detect thread language. Detect when a thread is in a non-English language and offer translation + simplification in one step.
- Surface coverage analytics. "Your team improved alt-text coverage from 12% to 78% this quarter." Make inclusion measurable.
Built With
- commonjs
- gemini
- javascript
- json
- node.js
- openai-sdk
- slack-block-kit
- slack-bolt-js
- socket-mode
Log in or sign up for Devpost to join the conversation.