Accessibility (A11y) Standards + Test Plan
Status: Reference
Scope note: the primary accessibility reference is UI/UX & Accessibility. This doc is the focused WCAG 2.1 AA test-plan companion.
Production-readiness #12. Documents what TwoMore enforces mechanically, what requires manual device testing, and the test plan for screen-reader audits before public launch.
Standards we comply with
- WCAG 2.1 Level AA — international baseline
- Korean Web Accessibility Guidelines 2.1 — KCC-published, mostly WCAG-aligned with extra emphasis on screen-reader compatibility for the elderly user base (large in tennis demographics)
What's mechanically enforced
| Constraint | Enforcement |
|---|---|
Every Pressable has accessibilityLabel | TypeScript-required on the @twomore/ui Pressable primitive |
| Touch targets ≥ 44pt | Pressable variants size their outer container correctly |
| Disabled state announces "busy" | Pressable + Button set accessibilityState.disabled + busy |
| Color contrast on text + icons | Tamagui token system — all $text / $textMuted etc. designed against $background for WCAG AA |
| Focus ring on web | Pressable web build adds outline: 2px solid $primary on :focus-visible |
Korean copy in t() | Every UI string lives in i18n — no hardcoded English to confuse TalkBack 한국어 |
What's NOT mechanically enforced (manual gaps)
<Image>accessibility labels — RN's Image doesn't TS-enforce accessibilityLabel. Decorative images should beaccessible={false}; content images need a label. Current state: 5 known content Images missing labels (post-detail, edit-profile avatar, club-members avatar, create-club logo preview, club-settings logo). Fixed in adjacent commits to this doc — but the gap isn't structurally prevented.accessibilityRole="header"on page/section titles — without it TalkBack reads the title as plain text instead of "Heading: 클럽 설정". Inconsistently applied across screens.Focus management on Sheet/Modal open — when ConfirmSheet or any Sheet opens, focus should move to the sheet's first focusable element. Currently relies on RN's default behavior which is inconsistent.
Live region announcements — for realtime updates (new score posted, RSVP confirmed), TalkBack users don't hear the change unless they re-traverse. Should use
accessibilityLiveRegion="polite"on the affected text.Dynamic type / large fonts — most Text uses fixed
rolesizes. Korean Pretendard font scales but layout may break at 200% font size.
Manual test plan
Run BEFORE public launch on a real device with screen reader enabled.
Top 5 screens to audit (priority order)
Login + Onboarding — first impression; must be perfect
- Can a TalkBack user complete signup?
- Are the 3 PIPA consent checkboxes labeled + clearly required vs optional?
- Does the welcome → consent → display-name → play-style flow announce each step?
Home feed — the most-used screen
- SessionCard chip strip: are 형식, 경쟁/친선, 진행중 chips all readable?
- LiveSessionCard pulses: does the PulseDot get an accessibilityLabel ("실시간")?
- SegmentedTabs (오늘 / 이번 주 / 다음 달): tab selection announced?
- Bottom tabs (홈 / 클럽 / 경기 / 기록 / 프로필): selected state announced?
Session detail — the action surface
- RSVP button state announced (확정 → 취소)?
- Participant card with avatars: does each avatar identify the player?
- Admin section: are owner-only buttons clearly distinguishable?
Spectator scorecard LIVE tab — most complex layout
- "내 경기" chip: announced as part of the row, not skipped?
- ScoreEditSheet: focus moves to the score input on open?
- Pending-call callout: does it announce as an alert?
Privacy + Settings — legal compliance + PIPA
- 동의 항목 (required/optional) clearly distinguishable by TalkBack?
- "내 데이터 받기" button: full action description, not just label?
- "탈퇴" / 계정 삭제: warning state announced?
TalkBack-specific test scenarios
For each of the 5 screens:
- [ ] Swipe right traverses elements in expected reading order
- [ ] No element is skipped or announced twice
- [ ] All buttons announce their purpose, not just label (e.g. "참가 확정 버튼")
- [ ] Headings announced as "Heading"
- [ ] Disabled buttons announce "사용 불가"
- [ ] Loading states announce ("로딩 중...")
- [ ] Errors announce as alerts, not silent rendering
VoiceOver-specific
iOS-specific tests when iOS preview build lands:
- [ ] Rotor navigation works (Headings, Buttons, Form Controls)
- [ ] Custom controls (chip groups, sheet drag handles) expose proper traits
Known fixes deferred
- Image accessibilityLabel sweep — 5 files, fixable when device testing reveals the actual user impact (some images may turn out to be decorative duplicates of nearby text)
accessibilityRole="header"sweep — every<Text role="pageTitle">could auto-set this; needs a UI primitive change- Live region announcements on score updates — depends on a device test to confirm what TalkBack users currently experience
Future enforcement (when device testing is available)
- ESLint rule:
<Image>withoutaccessibilityLabelAND withoutaccessible={false}is a lint error - ESLint rule:
<Text role="pageTitle">must also setaccessibilityRole="header"(or have a UI primitive do it) - Storybook-style a11y addon if a visual catalog is built
Reference
- WCAG 2.1: https://www.w3.org/TR/WCAG21/
- Korean Web Content Accessibility Guidelines: https://www.wah.or.kr:444/board/kwcag.asp
- React Native Accessibility: https://reactnative.dev/docs/accessibility
- Expo TalkBack/VoiceOver testing: https://docs.expo.dev/guides/accessibility/