axiom-accessibility-diag について
iOS および macOS アプリのアクセシビリティ課題を診断・修正する開発者向けスキルです。VoiceOver、文字サイズ、コントラスト、タッチ領域、キーボード操作、視差効果の軽減、Assistive Access を扱います。
主な機能
- 01VoiceOver のラベルとナビゲーション確認
- 02Dynamic Type と文字サイズ調整
- 03色のコントラストに関する指針
- 04タッチ領域のサイズ確認
- 05キーボード操作と視差効果の軽減
- 06Assistive Access の設定
活用シーン
- 01アプリ画面をアクセシビリティ審査に備える
- 02VoiceOver のラベルや読み上げ順を修正
- 03大きな文字や高コントラストでレイアウトを検証
- 04キーボード操作や視差効果の軽減に対応
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ charleswiltgen/ axiom --skill axiom-accessibility-diagインストール後、Claude Code で/axiom-accessibility-diagを入力して読み込まれたか確認します。
詳細
アクセシビリティ診断
概要
iOS/macOS アプリのアクセシビリティを体系的に診断し、改善します。App Store でのリジェクトやユーザーからの苦情につながる、最も一般的な 7 つのアクセシビリティ問題を取り上げます。
基本原則 アクセシビリティは任意ではありません。App Store の審査に合格するには、iOS アプリが VoiceOver、Dynamic Type、十分な色のコントラストに対応している必要があります。障害のあるユーザーは、これらの機能を必要としています。
このスキルを使用する場面
- VoiceOver のナビゲーション問題を修正する(ラベルがない、要素の順序が間違っている)
- Dynamic Type に対応する(視覚障害のあるユーザー向けのテキスト拡大)
- 色のコントラスト要件を満たす(WCAG AA/AAA)
- タッチターゲットサイズの違反を修正する(< 44x44pt)
- キーボードナビゲーションを追加する(iPadOS/macOS)
- 視覚運動障害に配慮して「視差効果を減らす」に対応する
- App Store のアクセシビリティ要件に備える
- アクセシビリティに関するユーザーの苦情に対応する
重大なアクセシビリティ問題 7 件
1. VoiceOver のラベルとヒント(重大 — App Store でのリジェクト)
問題 アクセシビリティラベルがない、または汎用的なラベルでは、VoiceOver ユーザーが UI の目的を理解できません。
WCAG 4.1.2 名前、役割、値(レベル A)
よくある違反
// ❌ WRONG - No label (VoiceOver says "Button")
Button(action: addToCart) {
Image(systemName: "cart.badge.plus")
}
// ❌ WRONG - Generic label
.accessibilityLabel("Button")
// ❌ WRONG - Reads implementation details
.accessibilityLabel("cart.badge.plus") // VoiceOver: "cart dot badge dot plus"
// ✅ CORRECT - Descriptive label
Button(action: addToCart) {
Image(systemName: "cart.badge.plus")
}
.accessibilityLabel("Add to cart")
// ✅ CORRECT - With hint for complex actions
.accessibilityLabel("Add to cart")
.accessibilityHint("Double-tap to add this item to your shopping cart")
ヒントを使用する場面
- ラベルから操作内容が明らかでない場合(「カートに追加」は明らかなため、ヒントは不要です)
- 複数段階の操作(「右にスワイプして確定、左にスワイプしてキャンセル」)
- 状態の変更(「ダブルタップして通知をオンまたはオフに切り替えます」)
装飾要素
// ✅ CORRECT - Hide decorative images from VoiceOver
Image("decorative-pattern")
.accessibilityHidden(true)
// ✅ CORRECT - Combine multiple elements into one label
HStack {
Image(systemName: "star.fill")
Text("4.5")
Text("(234 reviews)")
}
.accessibilityElement(children: .combine)
.accessibilityLabel("Rating: 4.5 stars from 234 reviews")
テスト
- VoiceOver を有効にする:Cmd+F5(シミュレーター)またはサイドボタンを 3 回クリック(デバイス)
- ナビゲーションする:右または左にスワイプして要素間を移動します
- 読み上げを確認する:VoiceOver は目的を明確に読み上げますか?
- 順序を確認する:ナビゲーションの順序は画面上のレイアウトと一致していますか?
2. Dynamic Type への対応(高 — ユーザー体験)
問題 フォントサイズが固定されていると、視覚障害のあるユーザーはテキストを読めません。
WCAG 1.4.4 テキストのサイズ変更(レベル AA — コンテンツや機能を損なわずに 200% の拡大に対応)
よくある違反
// ❌ WRONG - Fixed size, won't scale
Text("Price: $19.99")
.font(.system(size: 17))
UILabel().font = UIFont.systemFont(ofSize: 17)
// ❌ WRONG - Custom font without scaling
Text("Headline")
.font(Font.custom("CustomFont", size: 24))
// ✅ CORRECT - SwiftUI semantic styles (auto-scales)
Text("Price: $19.99")
.font(.body)
Text("Headline")
.font(.headline)
// ✅ CORRECT - UIKit semantic styles
label.font = UIFont.preferredFont(forTextStyle: .body)
// ✅ CORRECT - Custom font with scaling
let customFont = UIFont(name: "CustomFont", size: 24)!
label.font = UIFontMetrics.default.scaledFont(for: customFont)
label.adjustsFontForContentSizeCategory = true
Dynamic Type に合わせて拡大するカスタムサイズ
// ❌ WRONG - Fixed size, won't scale
Text("Price: $19.99")
.font(.system(size: 17))
// ⚠️ ACCEPTABLE - Custom font without scaling (accessibility violation)
Text("Headline")
.font(Font.custom("CustomFont", size: 24))
// ✅ GOOD - Custom size that scales with Dynamic Type
Text("Large Title")
.font(.system(size: 60).relativeTo(.largeTitle))
Text("Custom Headline")
.font(.system(size: 24).relativeTo(.title2))
// ✅ BEST - Use semantic styles when possible
Text("Headline")
.font(.headline)
relativeTo: の仕組み
- 基本サイズ:指定した正確なポイントサイズ(24pt、60pt など)
- 拡大の基準:指定したテキストスタイル(
.title2、.largeTitleなど) - 結果:ユーザーが「設定」でテキストサイズを大きくすると、カスタムサイズも比例して大きくなります
例
.title2の基本サイズ:約 22pt → カスタムサイズ:24pt(1.09 倍)- ユーザーがテキストサイズを「特大」に変更
.title2は約 28pt に拡大 → カスタムサイズは約 30.5pt に拡大(1.09 倍の比率を維持)
改善方法(最善から最悪の順)
- 最善:セマンティックスタイル(
.title、.body、.caption)を使用する - 良好:カスタムサイズが必要な場合は
.system(size:).relativeTo()を使用する - 許容可能:
.dynamicTypeSize()修飾子をカスタムフォントに使用する - 許容不可:拡大しない固定サイズ
SwiftUI のテキストスタイル
.largeTitle- 34pt(アクセシビリティサイズでは 44pt に拡大).title- 28pt.title2- 22pt.title3- 20pt.headline- 17pt セミボールド.body- 17pt(デフォルト).callout- 16pt.subheadline- 15pt.footnote- 13pt.caption- 12pt.caption2- 11pt
レイアウトに関する考慮事項
// ❌ WRONG - Fixed frame breaks with large text
Text("Long product description...")
.font(.body)
.frame(height: 50) // Clips at large text sizes
// ✅ CORRECT - Flexible frame
Text("Long product description...")
.font(.body)
.lineLimit(nil) // Allow multiple lines
.fixedSize(horizontal: false, vertical: true)
// ✅ CORRECT - Stack rearranges at large sizes
HStack {
Text("Label:")
Text("Value")
}
.dynamicTypeSize(...DynamicTypeSize.xxxLarge) // Limit maximum size if needed
テスト
-
Xcode プレビュー:環境のオーバーライド
.environment(\.sizeCategory, .accessibilityExtraExtraExtraLarge) -
シミュレーター:「設定」→「アクセシビリティ」→「画面表示とテキストサイズ」→「さらに大きな文字」→ 最大までドラッグ
-
デバイス:「設定」→「アクセシビリティ」→「画面表示とテキストサイズ」→「さらに大きな文字」
-
確認:テキストは読みやすい状態を保っていますか?レイアウトは適応していますか?テキストが切れていませんか?
3. 色のコントラスト(高 — 視覚障害)
問題 コントラストの低いテキストは、視覚障害のあるユーザーや、明るい日差しの下では読めません。
WCAG
- 1.4.3 コントラスト(最低限) — レベル AA
- 通常のテキスト(< 18pt):コントラスト比 4.5:1
- 大きなテキスト(≥ 18pt または ≥ 14pt の太字):コントラスト比 3:1
- 1.4.6 コントラスト(高度) — レベル AAA
- 通常のテキスト:コントラスト比 7:1
- 大きなテキスト:コントラスト比 4.5:1
よくある違反
// ❌ WRONG - Low contrast (1.8:1 - fails WCAG)
Text("Warning")
.foregroundColor(.yellow) // on white background
// ❌ WRONG - Low contrast in dark mode
Text("Info")
.foregroundColor(.gray) // on black background
// ✅ CORRECT - High contrast (7:1+ passes AAA)
Text("Warning")
.foregroundColor(.orange) // or .red
// ✅ CORRECT - System colors adapt to light/dark mode
Text("Info")
.foregroundColor(.primary) // Black in light mode, white in dark
Text("Secondary")
.foregroundColor(.secondary) // Automatic high contrast
色だけに頼らず区別する
// ❌ WRONG - Color alone indicates status
Circle()
.fill(isAvailable ? .green : .red)
// ✅ CORRECT - Color + icon/text
HStack {
Image(systemName: isAvailable ? "checkmark.circle.fill" : "xmark.circle.fill")
Text(isAvailable ? "Available" : "Unavailable")
}
.foregroundColor(isAvailable ? .green : .red)
// ✅ CORRECT - Respect system preference
if UIAccessibility.shouldDifferentiateWithoutColor {
// Use patterns, icons, or text instead of color alone
}
テスト
- Color Contrast Analyzer ツールを使用します(無料でダウンロードできます)
- UI のスクリーンショットを撮り、テキストと背景のコントラストを測定します
- ライトモードとダークモードの両方を確認します
- 設定 → アクセシビリティ → 画面表示とテキストサイズ → コントラストを上げる(オンにしてテストします)
クイックリファレンス
- 白(#FFFFFF)に黒(#000000):21:1 ✅ AAA
- 白にダークグレー(#595959):7:1 ✅ AAA
- 白にミディアムグレー(#767676):4.5:1 ✅ AA
- 白にライトグレー(#959595):2.8:1 ❌ 不合格
4. タッチターゲットのサイズ(中 — 運動障害)
問題 タップターゲットが小さいと、運動障害のあるユーザーにとって操作が困難、または不可能になります。
WCAG 2.5.5 ターゲットサイズ(レベル AAA — 最小 44x44pt)
Apple HIG タップ可能なすべての要素で最小 44x44pt
よくある違反
// ❌ WRONG - Too small (24x24pt)
Button("×") {
dismiss()
}
.frame(width: 24, height: 24)
// ❌ WRONG - Small icon without padding
Image(systemName: "heart")
.font(.system(size: 16))
.onTapGesture { }
// ✅ CORRECT - Minimum 44x44pt
Button("×") {
dismiss()
}
.frame(minWidth: 44, minHeight: 44)
// ✅ CORRECT - Larger icon or padding
Image(systemName: "heart")
.font(.system(size: 24))
.frame(minWidth: 44, minHeight: 44)
.contentShape(Rectangle()) // Expand tap area
.onTapGesture { }
// ✅ CORRECT - UIKit button with edge insets
button.contentEdgeInsets = UIEdgeInsets(top: 12, left: 12, bottom: 12, right: 12)
// Total size: icon size + insets ≥ 44x44pt
ターゲット間の間隔
// ❌ WRONG - Targets too close (hard to tap accurately)
HStack(spacing: 4) {
Button("Edit") { }
Button("Delete") { }
}
// ✅ CORRECT - Adequate spacing (8pt minimum, 12pt better)
HStack(spacing: 12) {
Button("Edit") { }
Button("Delete") { }
}
テスト
- Accessibility Inspector:Xcode → 開発者ツールを開く → Accessibility Inspector
- 「Audit」タブを選択 → 監査を実行 → 「Small Text」と「Hit Region」の警告を確認します
- 手動:スタイラスではなく指でタップし、間違えずに確実に操作できますか?
5. キーボードナビゲーション(中 — iPadOS/macOS)
問題 タッチやマウスを使用できないユーザーは、アプリ内を移動できません。
WCAG 2.1.1 キーボード(レベル A — すべての機能をキーボードで利用可能)
よくある違反
// ❌ WRONG - Custom gesture without keyboard alternative
.onTapGesture {
showDetails()
}
// No way to trigger with keyboard
// ✅ CORRECT - Button provides keyboard support automatically
Button("Show Details") {
showDetails()
}
.keyboardShortcut("d", modifiers: .command) // Optional shortcut
// ✅ CORRECT - Custom control with focus support
struct CustomButton: View {
@FocusState private var isFocused: Bool
var body: some View {
Text("Custom")
.focusable()
.focused($isFocused)
.onKeyPress(.return) {
action()
return .handled
}
}
}
フォーカス管理
// ✅ CORRECT - Set initial focus
.focusSection() // Group related controls
.defaultFocus($focus, .constant(true)) // Set default
// ✅ CORRECT - Move focus after action
@FocusState private var focusedField: Field?
Button("Next") {
focusedField = .next
}
テスト(iPadOS/macOS)
- iPad にキーボードを接続するか、Mac を使用します
- Tab キーを押すと、フォーカスはインタラクティブな要素に移動しますか?
- Space/Return キーを押すと、フォーカスされた要素は動作しますか?
- カスタムコントロールに視認可能なフォーカスインジケーターがあることを確認します
- マウスやタッチを使わずに、すべての機能にアクセスできますか?
6. 視差効果を減らす設定への対応(中 — 前庭障害)
問題 アニメーションは、前庭障害のあるユーザーに不快感、吐き気、または発作を引き起こすことがあります。
WCAG 2.3.3 インタラクションによるアニメーション(レベル AAA — 動きのあるアニメーションを無効にできます)
よくある違反
// ❌ WRONG - Always animates (can cause nausea)
.onAppear {
withAnimation(.spring(response: 0.6, dampingFraction: 0.8)) {
scale = 1.0
}
}
// ❌ WRONG - Parallax scrolling without opt-out
ScrollView {
GeometryReader { geo in
Image("hero")
.offset(y: geo.frame(in: .global).minY * 0.5) // Parallax
}
}
// ✅ CORRECT - Respect Reduce Motion preference
.onAppear {
if UIAccessibility.isReduceMotionEnabled {
scale = 1.0 // Instant
} else {
withAnimation(.spring(response: 0.6, dampingFraction: 0.8)) {
scale = 1.0
}
}
}
// ✅ CORRECT - Simpler animation or cross-fade
if UIAccessibility.isReduceMotionEnabled {
// Cross-fade or instant change
withAnimation(.linear(duration: 0.2)) {
showView = true
}
} else {
// Complex spring animation
withAnimation(.spring()) {
showView = true
}
}
SwiftUI モディファイア
// ✅ CORRECT - Automatic support
.animation(.spring(), value: isExpanded)
.transaction { transaction in
if UIAccessibility.isReduceMotionEnabled {
transaction.animation = nil // Disable animation
}
}
テスト
- 設定 → アクセシビリティ → 動作 → 視差効果を減らす(オンにします)
- アプリ内を移動し、アニメーションが軽減または無効になっているか確認します
- トランジション、スクロール効果、視差効果、パーティクル効果をテストします
- 動画の自動再生も、この設定に従う必要があります
7. よくある違反(高 — App Store 審査)
ラベルのない画像
// ❌ WRONG - Informative image without label
Image("product-photo")
// ✅ CORRECT - Informative image with label
Image("product-photo")
.accessibilityLabel("Red sneakers with white laces")
// ✅ CORRECT - Decorative image hidden
Image("background-pattern")
.accessibilityHidden(true)
不適切な特性を持つボタン
// ❌ WRONG - Custom button without button trait
Text("Submit")
.onTapGesture {
submit()
}
// VoiceOver announces as "Submit, text" not "Submit, button"
// ✅ CORRECT - Use Button for button-like controls
Button("Submit") {
submit()
}
// VoiceOver announces as "Submit, button"
// ✅ CORRECT - Custom control with correct trait
Text("Submit")
.accessibilityAddTraits(.isButton)
.onTapGesture {
submit()
}
アクセスできないカスタムコントロール
// ❌ WRONG - Custom slider without accessibility support
struct CustomSlider: View {
@Binding var value: Double
var body: some View {
// Drag gesture only, no VoiceOver support
GeometryReader { geo in
// ...
}
.gesture(DragGesture()...)
}
}
// ✅ CORRECT - Custom slider with accessibility actions
struct CustomSlider: View {
@Binding var value: Double
var body: some View {
GeometryReader { geo in
// ...
}
.gesture(DragGesture()...)
.accessibilityElement()
.accessibilityLabel("Volume")
.accessibilityValue("\(Int(value))%")
.accessibilityAdjustableAction { direction in
switch direction {
case .increment:
value = min(value + 10, 100)
case .decrement:
value = max(value - 10, 0)
@unknown default:
break
}
}
}
}
状態の通知がない
// ❌ WRONG - State change without announcement
Button("Toggle") {
isOn.toggle()
}
// ✅ CORRECT - State change with announcement
Button("Toggle") {
isOn.toggle()
UIAccessibility.post(
notification: .announcement,
argument: isOn ? "Enabled" : "Disabled"
)
}
// ✅ CORRECT - Automatic state with accessibilityValue
Button("Toggle") {
isOn.toggle()
}
.accessibilityValue(isOn ? "Enabled" : "Disabled")
8. Assistive Access のサポート(iOS 17 以降 — 認知障害)
問題 アプリが Assistive Access モードで利用できない、または正しく動作せず、簡素化されたシステム環境を必要とする認知障害のあるユーザーが利用できなくなります。
Assistive Access はシステム全体のモードです(設定 > アクセシビリティ > Assistive Access)。標準の iOS UI を、大きなコントロール、簡素化されたナビゲーション、認知負荷を軽減した UI に置き換えます。このモードを利用できるように設定していないアプリは、ユーザーに表示されません。
症状:Assistive Access のホーム画面にアプリが表示されない
Assistive Access の設定で、アプリが「最適化されたアプリ」に表示されません。
<key>UISupportsAssistiveAccess</key>
<true/>
これにより、Assistive Access モードでアプリを利用でき、フルスクリーンで起動します。このキーがないと、Assistive Access モードのユーザーはアプリにアクセスできません。
症状:標準 UI が Assistive Access のユーザーには複雑すぎる
アプリは Assistive Access で起動しますが、標準のインターフェイス全体が表示されるため、簡素化されたコントロールを必要とするユーザーを圧倒してしまいます。
// ✅ FIX - Provide a dedicated Assistive Access scene
@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
ContentView() // Standard UI
}
AssistiveAccess {
AssistiveAccessContentView() // Simplified UI
}
}
}
AssistiveAccess シーンタイプは、別のエントリーポイントを提供します。システムが Assistive Access モードのときは、標準の WindowGroup の代わりにこのシーンが使用されます。このシーン内のネイティブな SwiftUI コントロールは、大きなボタン、目立つナビゲーション、グリッド/行レイアウトなど、Assistive Access の視覚スタイルを自動的に採用します。
症状:認知アクセシビリティ向けにすでに設計されたアプリが、縮小されたフレーム内に表示される
アプリが認知障害のあるユーザー向けにすでに特化して設計されている場合(AAC アプリなど)、フルスクリーンではなく縮小されたフレーム内に表示されることがあります。
<key>UISupportsFullScreenInAssistiveAccess</key>
<true/>
これにより、Assistive Access のフレームを経由せず、アプリが通常の表示と同じ状態で表示されます。
実行時に Assistive Access を検出する
struct MyView: View {
@Environment(\.accessibilityAssistiveAccessEnabled) var assistiveAccessEnabled
var body: some View {
if assistiveAccessEnabled {
// Simplified content
} else {
// Standard content
}
}
}
UIKit の実装
UIKit アプリでは、UISceneConfiguration の .windowAssistiveAccessApplication シーンセッションロールを使用して、Assistive Access 用の専用シーンデリゲートにルーティングします。
Assistive Access シーンの設計原則
- 中核機能に絞る — アプリ全体ではなく、不可欠な機能を 1 つまたは 2 つに絞ります
- 大きく目立つコントロール — 十分な間隔を設け、隠しジェスチャや時間制限のある操作は使いません
- 複数の表現方法 — テキストにアイコンを組み合わせ、視覚的な代替手段を用意します
- 段階的なナビゲーション — 明確な戻るボタンと一貫したパターンを使用します
- 安全な操作 — 取り消せない操作をなくし、破壊的な操作には確認を求めます
ナビゲーションアイコンの追加
NavigationStack {
MyView()
.navigationTitle("My Feature")
.assistiveAccessNavigationIcon(systemImage: "star.fill")
}
テスト
- デバイス — 「設定」>「アクセシビリティ」>「Assistive Access」で Assistive Access を有効にし、アプリが「最適化されたアプリ」に表示されることを確認して、ユーザーフロー全体をテストします
- Accessibility Inspector — Assistive Access シーンを監査し、ラベル、コントラスト、タップ領域の問題を確認します
Accessibility Inspector のワークフロー
1. Accessibility Inspector を起動する
Xcode →「Open Developer Tool」→「Accessibility Inspector」
2. ターゲットを選択する
- ドロップダウン:実行中のシミュレーターまたは接続済みのデバイスを選択します
- ターゲット:アプリを選択します
3. 検査モード
- 「Inspection Pointer」ボタン(十字アイコン)をクリックします
- UI 要素にカーソルを合わせると、次の情報が表示されます:
- ラベル、値、ヒント、特性
- フレーム、パス
- 利用可能なアクション
- 親子階層
4. 監査を実行する
- 「Audit」タブをクリックします
- 「Run Audit」ボタンをクリックします
- 検出結果を確認します:
- コントラスト — 色のコントラストに関する問題
- タップ領域 — タッチターゲットのサイズに関する問題
- テキストの切り詰め — Dynamic Type によるテキストの切り詰め
- 要素の説明 — ラベルやヒントの欠落
- 特性 — アクセシビリティ特性の誤り
5. 修正して再テストする
- 各検出結果をクリックして詳細を確認します
- コードを修正します
- 監査を再実行して確認します
VoiceOver テストチェックリスト
VoiceOver を有効にする
- シミュレーター Cmd+F5 または「設定」→「アクセシビリティ」→「VoiceOver」
- デバイス サイドボタンをトリプルクリックします(「設定」で有効にしている場合)
ナビゲーションのテスト
- ☐ 右/左にスワイプすると、UI 要素を論理的な順序で移動します
- ☐ 各要素が目的を明確に読み上げます
- ☐ ラベルのない要素がありません(装飾要素を除く)
- ☐ 見出しナビゲーションが動作します(2 本指で上下にスワイプ)
- ☐ コンテナナビゲーションが動作します(3 本指で左右にスワイプ)
操作のテスト
- ☐ ダブルタップするとボタンが有効になります
- ☐
.accessibilityAdjustableActionを使用している場合、上下にスワイプするとスライダーやピッカーの値が調整されます - ☐ カスタムジェスチャに VoiceOver で使える代替操作があります
- ☐ テキストフィールドがキーボードの種類を読み上げます
- ☐ 状態の変化が読み上げられます
コンテンツのテスト
- ☐ 画像に説明ラベルが付いているか、非表示になっています
- ☐ エラーメッセージが読み上げられます
- ☐ 読み込み状態が読み上げられます
- ☐ モーダルシートが役割を読み上げます
- ☐ アラートが自動的に読み上げられます
App Store 審査の準備
必須のアクセシビリティ機能(iOS)
-
VoiceOver サポート
- すべての UI 要素にラベルを設定する必要があります
- ナビゲーションは論理的である必要があります
- すべてのアクションを実行できる必要があります
-
Dynamic Type
- テキストは -3 から +12 までのサイズに拡大縮小できる必要があります
- レイアウトはテキストが切り詰められずに適応する必要があります
-
十分なコントラスト
- 通常のテキストは最低 4.5:1
- 大きなテキスト(≥18pt)は最低 3:1
App Store Connect のメタデータ
提出時:
-
「アクセシビリティ」→ アプリがサポートする機能を選択します:
- ☑ VoiceOver
- ☑ Dynamic Type
- ☑ コントラストを上げる
- ☑ 視差効果を減らす(サポートしている場合)
-
テストノート:アクセシビリティテストについて記載します
Accessibility Testing Completed: - VoiceOver: All screens tested with VoiceOver enabled - Dynamic Type: Tested at all size categories - Color Contrast: Verified 4.5:1 minimum contrast - Touch Targets: All buttons minimum 44x44pt - Reduce Motion: Animations respect user preference
よくある却下理由
-
「VoiceOver でアプリのすべての機能を利用できません」
- 画像/ボタンにラベルがない
- カスタムコントロールにラベルがない
- VoiceOver で操作を実行できない
-
「Dynamic Type のすべての文字サイズでテキストを読めません」
- フォントサイズが固定されている
- 文字サイズを大きくするとテキストが切れる
- アクセシビリティ用の文字サイズでレイアウトが崩れる
-
「色のコントラストが不十分です」
- テキストが 4.5:1 の比率を満たしていない
- UI 要素が 3:1 の比率を満たしていない
- 色だけで状態を示している
デザインレビューのプレッシャー:アクセシビリティ要件を守る
問題点
デザインレビューでプレッシャーを受けると、次のような要望に直面します。
- 「VoiceOver のラベルでコードが煩雑になるので、省略できませんか?」
- 「Dynamic Type だと、慎重に設計したレイアウトが崩れるので、フォントサイズを固定しましょう」
- 「高コントラスト要件のせいで、ブランドの美しさが損なわれます」
- 「44pt のタップ領域は大きすぎて見た目が悪いので、もっと小さくしてください」
これらはもっともらしいデザイン上の好みに聞こえます。**しかし、App Store の要件に違反し、ユーザーの 15% を排除することになります。**あなたの役割は、意見ではなく App Store のガイドラインと法的要件を根拠に、要件を守ることです。
注意すべき兆候 — アクセシビリティに違反するデザイナーからの要望
次のいずれかを聞いたら、作業を止め、このスキルを参照してください。
- ❌ 「アイコンだけのボタンには VoiceOver のラベルを付けない」 – App Store で却下されます(ガイドライン 2.5.1)
- ❌ 「コンパクトなデザインには 14pt の固定フォントを使う」 – 視覚障害のあるユーザーを排除します
- ❌ 「コントラスト比は 3:1 で問題ない」 – テキストでは WCAG AA の要件を満たしません(4.5:1 が必要です)
- ❌ 「見た目をすっきりさせるため、ボタンを 36x36pt にする」 – タップ領域の要件を満たしません(最小 44x44pt)
- ❌ 「この画面では Dynamic Type を無効にする」 – App Store で却下されるリスクがあります
- ❌ 「ラベルを付けずに色で分類する(赤=エラー、緑=成功)」 – 色覚異常のあるユーザーを排除します(男性の 8%)
プロとして異議を伝える方法
ステップ 1:ガイドラインを示す
"I want to support this design direction, but let me show you Apple's App Store
Review Guideline 2.5.1:
'Apps should support accessibility features such as VoiceOver and Dynamic Type.
Failure to include sufficient accessibility features may result in rejection.'
Here's what we need for approval:
1. VoiceOver labels on all interactive elements
2. Dynamic Type support (can't lock font sizes)
3. 4.5:1 contrast ratio for text, 3:1 for UI
4. 44x44pt minimum touch targets
Let me show where our design currently falls short..."
ステップ 2:リスクを実証する
アクセシビリティ機能を有効にして、アプリを開きます。
- VoiceOver(Cmd+F5):ボタンが目的ではなく「ボタン」と読み上げられることを示す
- 最大の文字サイズ:レイアウトの崩れやテキストの切れを示す
- カラーコントラストアナライザー:コントラスト比が要件を満たしていないことを示す
- タップ領域のオーバーレイ:44pt 未満の領域を示す
参照先
- App Store 審査ガイドライン 2.5.1
- WCAG 2.1 レベル AA(業界標準)
- ADA 準拠要件(米国での法的リスク)
ステップ 3:妥協案を提案する
"I can achieve your aesthetic goals while meeting accessibility requirements:
1. VoiceOver labels: Add them programmatically (invisible in UI, required for approval)
2. Dynamic Type: Use layout techniques that adapt (examples from Apple HIG)
3. Contrast: Adjust colors slightly to meet 4.5:1 (I'll show options that preserve brand)
4. Touch targets: Expand hit areas programmatically (visual size stays the same)
These changes won't affect the visual design you're seeing, but they're required
for App Store approval and legal compliance."
ステップ 4:決定を記録する
却下された場合(デザイナーが違反を譲らない場合):
Slack message to PM + designer:
"Design review decided to proceed with:
- Fixed font sizes (disabling Dynamic Type)
- 38x38pt buttons (below 44pt requirement)
- 3.8:1 text contrast (below 4.5:1 requirement)
Important: These changes violate App Store Review Guideline 2.5.1 and WCAG AA.
This creates three risks:
1. App Store rejection during review (adds 1-2 week delay)
2. ADA compliance issues if user files complaint (legal risk)
3. 15% of potential users unable to use app effectively
I'm flagging this proactively so we can prepare a response plan if rejected."
効果がある理由
- デザインの好みを否定しているわけではありません
- App Store で却下されるリスク(ビジネスへの影響)を伝えています
- 具体的なガイドラインを引用しています(意見ではありません)
- ビジュアルデザインを保つ解決策を提案しています
- 決定を記録しています(却下後に自分を守れます)
実例:App Store での却下(48 時間以内の再提出期限)
状況
- 却下後、再提出期限まで 48 時間
- Apple からの指摘:「2.5.1 - VoiceOver のサポートが不十分」
- デザイナーの発言:「汎用的なラベルをさっと追加すればいい」
- 会議には PM も同席し、最速の修正を求めている
対応方法
// ❌ WRONG - Generic labels (will fail re-review)
Button(action: addToCart) {
Image(systemName: "cart.badge.plus")
}
.accessibilityLabel("Button") // Apple will reject again
// ✅ CORRECT - Descriptive labels (passes review)
Button(action: addToCart) {
Image(systemName: "cart.badge.plus")
}
.accessibilityLabel("Add to cart")
.accessibilityHint("Double-tap to add this item to your shopping cart")
会議で実演する内容
- VoiceOver を有効にする(Cmd+F5)
- 「ボタン」と読み上げられることを示す(汎用的なラベルで、不合格)
- 「カートに追加」と読み上げられることを示す(具体的なラベルで、合格)
- Apple の却下メッセージを引用する:「要素には説明的なラベルが必要です」
所要時間の見積もり すべてのインタラクティブ要素を監査し、適切なラベルを追加するのに 2~4 時間。
結果
- 正直な所要時間の見積もりにより、再度の却下を防げます
- 適切なラベルで Apple の審査に合格します
- 48 時間以内に再提出が承認されます
デザイン上の決定を受け入れる場合(同意できなくても)
デザイナーがアクセシビリティのガイドラインに従わない正当な理由を持つ場合もあります。次の条件を満たすなら、受け入れてください。
- App Store で却下されるリスクを理解している
- 却下された場合、リリースを延期する意思がある
- 決定を書面で記録する
- 却下された場合に修正することを約束している
Slack に記録する
"Design review decided to proceed with [specific violations].
We understand this creates:
- App Store rejection risk (Guideline 2.5.1)
- Potential 1-2 week delay if rejected
- Need to audit and fix all instances if rejected
Monitoring plan:
- Submit for review with current design
- If rejected, implement proper accessibility (estimated 2-4 hours)
- Have accessibility-compliant version ready as backup"
これにより双方を守り、あなたが作業を妨げているのではなく、リスクを減らそうとしていることを示せます。
WCAG 準拠レベル
レベル A(最低限 — App Store で必須)
- 1.1.1 非テキストコンテンツ — 画像にテキストによる代替情報を用意します
- 2.1.1 キーボード — すべての機能をキーボードで操作できるようにします(iPadOS/macOS)
- 4.1.2 名前、役割、値 — 要素にアクセシブルな名前を設定します
レベル AA(標準 — 推奨)
- 1.4.3 コントラスト(最低限) — テキストは 4.5:1、UI は 3:1 にします
- 1.4.4 テキストのサイズ変更 — テキストの 200% 拡大をサポートします
- 1.4.5 テキストの画像 — 可能な限り実際のテキストを使用します
レベル AAA(強化 — ベストプラクティス)
- 1.4.6 コントラスト(強化) — テキストは 7:1、UI は 4.5:1 にします
- 2.3.3 インタラクションによるアニメーション — 視差効果を減らす設定をサポートします
- 2.5.5 ターゲットサイズ - 最小 44x44pt のターゲット
目標 すべてのコンテンツでレベル AA を満たし、可能な場合はレベル AAA を満たします。
コマンドのクイックリファレンス
修正を行った後:
# Quick scan for new issues
/axiom:audit-accessibility
# Deep diagnosis for specific issues
/skill axiom:accessibility-diag
リソース
ドキュメント:/accessibility/voiceover, /uikit/uifont/scaling_fonts_automatically
覚えておいてください アクセシビリティは機能ではなく、必須要件です。ユーザーの 15% は何らかの障害を抱えています。アプリをアクセシブルにすることは、正しいことをするだけでなく、ユーザー層を広げ、すべての人の利用体験を向上させます。