jpskill.com
🛠️ 開発・MCP コミュニティ

accessibility-patterns

AndroidとiOSのモバイルアプリにおける、コンテンツの説明、タッチターゲット、スクリーンリーダー対応、WCAG準拠、文字サイズ調整、色のコントラストなど、アクセシビリティに関する様々なパターンを適用するSkill。

📜 元の英語説明(参考)

Mobile accessibility patterns for Android and iOS - content descriptions, touch targets, screen reader support, WCAG compliance, dynamic type, and color contrast.

🇯🇵 日本人クリエイター向け解説

一言でいうと

AndroidとiOSのモバイルアプリにおける、コンテンツの説明、タッチターゲット、スクリーンリーダー対応、WCAG準拠、文字サイズ調整、色のコントラストなど、アクセシビリティに関する様々なパターンを適用するSkill。

※ jpskill.com 編集部が日本のビジネス現場向けに補足した解説です。Skill本体の挙動とは独立した参考情報です。

⚡ おすすめ: コマンド1行でインストール(60秒)

下記のコマンドをコピーしてターミナル(Mac/Linux)または PowerShell(Windows)に貼り付けてください。 ダウンロード → 解凍 → 配置まで全自動。

🍎 Mac / 🐧 Linux
mkdir -p ~/.claude/skills && cd ~/.claude/skills && curl -L -o accessibility-patterns.zip https://jpskill.com/download/16399.zip && unzip -o accessibility-patterns.zip && rm accessibility-patterns.zip
🪟 Windows (PowerShell)
$d = "$env:USERPROFILE\.claude\skills"; ni -Force -ItemType Directory $d | Out-Null; iwr https://jpskill.com/download/16399.zip -OutFile "$d\accessibility-patterns.zip"; Expand-Archive "$d\accessibility-patterns.zip" -DestinationPath $d -Force; ri "$d\accessibility-patterns.zip"

完了後、Claude Code を再起動 → 普通に「動画プロンプト作って」のように話しかけるだけで自動発動します。

💾 手動でダウンロードしたい(コマンドが難しい人向け)
  1. 1. 下の青いボタンを押して accessibility-patterns.zip をダウンロード
  2. 2. ZIPファイルをダブルクリックで解凍 → accessibility-patterns フォルダができる
  3. 3. そのフォルダを C:\Users\あなたの名前\.claude\skills\(Win)または ~/.claude/skills/(Mac)へ移動
  4. 4. Claude Code を再起動

⚠️ ダウンロード・利用は自己責任でお願いします。当サイトは内容・動作・安全性について責任を負いません。

🎯 このSkillでできること

下記の説明文を読むと、このSkillがあなたに何をしてくれるかが分かります。Claudeにこの分野の依頼をすると、自動で発動します。

📦 インストール方法 (3ステップ)

  1. 1. 上の「ダウンロード」ボタンを押して .skill ファイルを取得
  2. 2. ファイル名の拡張子を .skill から .zip に変えて展開(macは自動展開可)
  3. 3. 展開してできたフォルダを、ホームフォルダの .claude/skills/ に置く
    • · macOS / Linux: ~/.claude/skills/
    • · Windows: %USERPROFILE%\.claude\skills\

Claude Code を再起動すれば完了。「このSkillを使って…」と話しかけなくても、関連する依頼で自動的に呼び出されます。

詳しい使い方ガイドを見る →
最終更新
2026-05-18
取得日時
2026-05-18
同梱ファイル
1

📖 Skill本文(日本語訳)

※ 原文(英語/中国語)を Gemini で日本語化したものです。Claude 自身は原文を読みます。誤訳がある場合は原文をご確認ください。

モバイルアクセシビリティパターン

Android / Jetpack Compose

Content Descriptions

// 画像とアイコンは content description を持つ必要があります
Image(
    painter = painterResource(R.drawable.profile_photo),
    contentDescription = "User profile photo"
)

Icon(
    imageVector = Icons.Default.Settings,
    contentDescription = "Open settings"
)

// 装飾的な要素は null を使用します
Image(
    painter = painterResource(R.drawable.decorative_divider),
    contentDescription = null // 純粋に装飾的
)

Semantics Modifier

// カスタムのセマンティックプロパティ
Box(
    modifier = Modifier.semantics {
        contentDescription = "User card for Alice Johnson, 3 unread messages"
        stateDescription = "Expanded"
    }
)

// スクリーンリーダーナビゲーション用の見出し階層
Text(
    text = "Account Settings",
    style = MaterialTheme.typography.headlineMedium,
    modifier = Modifier.semantics { heading() }
)

// 動的なコンテンツ更新のためのライブリージョン
Text(
    text = "Items in cart: $count",
    modifier = Modifier.semantics {
        liveRegion = LiveRegionMode.Polite
    }
)

// 重要な更新のためのアサーティブライブリージョン
Text(
    text = errorMessage,
    modifier = Modifier.semantics {
        liveRegion = LiveRegionMode.Assertive
    }
)

Minimum Touch Target Size

// Material 3 は、クリック可能な要素に対して自動的に 48dp の最小値を強制します
// カスタム要素の場合は、最小サイズを確保してください:
IconButton(
    onClick = { /* action */ },
    modifier = Modifier.sizeIn(minWidth = 48.dp, minHeight = 48.dp)
) {
    Icon(Icons.Default.Close, contentDescription = "Close dialog")
}

// 拡張されたタッチターゲットを持つ小さなアイコン
Box(
    modifier = Modifier
        .size(48.dp)
        .clickable(onClick = { /* action */ }),
    contentAlignment = Alignment.Center
) {
    Icon(
        imageVector = Icons.Default.Info,
        contentDescription = "More information",
        modifier = Modifier.size(24.dp)
    )
}

Grouping and Merging Semantics

// 子のセマンティクスを単一のアナウンスにマージします
Row(
    modifier = Modifier
        .clearAndSetSemantics {
            contentDescription = "Alice Johnson, Software Engineer, Online"
        }
        .clickable { onUserClick() }
) {
    Avatar(url = user.avatarUrl)
    Column {
        Text(user.name)
        Text(user.title)
    }
    OnlineIndicator(isOnline = user.isOnline)
}

// ロールの割り当て
Box(
    modifier = Modifier
        .clickable { onAction() }
        .semantics { role = Role.Button }
) {
    Text("Custom Button")
}

// タブのロール
Text(
    text = tabTitle,
    modifier = Modifier
        .clickable { onTabSelect() }
        .semantics {
            role = Role.Tab
            selected = isSelected
        }
)

Toggle and Switch Accessibility

Row(
    modifier = Modifier
        .toggleable(
            value = isEnabled,
            onValueChange = { onToggle(it) },
            role = Role.Switch
        )
        .padding(16.dp)
        .semantics {
            contentDescription = "Dark mode"
            stateDescription = if (isEnabled) "Enabled" else "Disabled"
        }
) {
    Text("Dark Mode", modifier = Modifier.weight(1f))
    Switch(checked = isEnabled, onCheckedChange = null) // null: Row handles toggle
}

Traversal Order

Column(
    modifier = Modifier.semantics {
        traversalIndex = 0f // 最初に読み込まれる
    }
) {
    Text("Important announcement")
}

Column(
    modifier = Modifier.semantics {
        traversalIndex = 1f // 2番目に読み込まれる
    }
) {
    Text("Secondary content")
}

iOS / SwiftUI

Accessibility Labels and Hints

Image(systemName: "gear")
    .accessibilityLabel("Settings")
    .accessibilityHint("Opens application settings")

Button(action: { deleteItem() }) {
    Image(systemName: "trash")
}
.accessibilityLabel("Delete item")
.accessibilityHint("Permanently removes this item from your list")

Combining Child Elements

HStack {
    Image(systemName: "person.circle")
    VStack(alignment: .leading) {
        Text(user.name)
        Text(user.role)
    }
}
.accessibilityElement(children: .combine) // 単一の要素として読み込まれる

// 子を無視し、カスタムラベルを提供する
HStack {
    StarRating(rating: 4.5)
}
.accessibilityElement(children: .ignore)
.accessibilityLabel("Rating: 4.5 out of 5 stars")

Dynamic Type Support

// 自動的に拡大縮小するシステムテキストスタイルを使用する
Text("Heading")
    .font(.title)

Text("Body text")
    .font(.body)

// 拡大縮小するカスタムサイズ
@ScaledMetric(relativeTo: .body) var iconSize: CGFloat = 24

Image(systemName: "star.fill")
    .frame(width: iconSize, height: iconSize)

// 固定の最小行の高さ
Text("Important text")
    .font(.body)
    .lineSpacing(4)

Reduce Motion Support

@Environment(\.accessibilityReduceMotion) var reduceMotion

var animation: Animation? {
    reduceMotion ? nil : .spring(response: 0.5)
}

Button("Animate") {
    withAnimation(animation) {
        isExpanded.toggle()
    }
}

VoiceOver Rotor Support

List(items) { item in
    ItemRow(item: item)
        .accessibilityRotorEntry(id: item.id, in: .headings)
}

// カスタムローター
.accessibilityRotor("Unread Messages") {
    ForEach(messages.filter(\.isUnread)) { message in
        AccessibilityRotorEntry(message.subject, id: message.id)
    }
}

Accessibility Actions

ItemRow(item: item)
    .accessibilityAction(named: "Delete") {
        deleteItem(item)
    }
    .accessibilityAction(named: "Mark as favorite") {
        toggleFavorite(item)
    }

Cross-Platform Patterns

Color Contrast Requirements (WCAG 2.1)

最小コントラスト比:
- 通常のテキスト:     4.5:1 (AA)
- 大きなテキスト:      3.0:1 (AA, >=18sp または >=14sp 太字)
- UI コンポーネント:   3.0:1 (AA)
- 拡張 (AAA):  通常のテキストで 7.0:1、大きなテキストで 4.5:1

一般的な合格する組み合わせ:
- #000000 on #FFFFFF -> 21:1 (すべて合格)
- #595959 on #FFFFFF -> 7.0:1 (AAA 合格)

(原文はここで切り詰められています)
📜 原文 SKILL.md(Claudeが読む英語/中国語)を展開

Mobile Accessibility Patterns

Android / Jetpack Compose

Content Descriptions

// Images and icons must have content descriptions
Image(
    painter = painterResource(R.drawable.profile_photo),
    contentDescription = "User profile photo"
)

Icon(
    imageVector = Icons.Default.Settings,
    contentDescription = "Open settings"
)

// Decorative elements use null
Image(
    painter = painterResource(R.drawable.decorative_divider),
    contentDescription = null // purely decorative
)

Semantics Modifier

// Custom semantic properties
Box(
    modifier = Modifier.semantics {
        contentDescription = "User card for Alice Johnson, 3 unread messages"
        stateDescription = "Expanded"
    }
)

// Heading hierarchy for screen reader navigation
Text(
    text = "Account Settings",
    style = MaterialTheme.typography.headlineMedium,
    modifier = Modifier.semantics { heading() }
)

// Live region for dynamic content updates
Text(
    text = "Items in cart: $count",
    modifier = Modifier.semantics {
        liveRegion = LiveRegionMode.Polite
    }
)

// Assertive live region for critical updates
Text(
    text = errorMessage,
    modifier = Modifier.semantics {
        liveRegion = LiveRegionMode.Assertive
    }
)

Minimum Touch Target Size

// Material 3 enforces 48dp minimum automatically for clickable elements
// For custom elements, ensure minimum size:
IconButton(
    onClick = { /* action */ },
    modifier = Modifier.sizeIn(minWidth = 48.dp, minHeight = 48.dp)
) {
    Icon(Icons.Default.Close, contentDescription = "Close dialog")
}

// Small icon with expanded touch target
Box(
    modifier = Modifier
        .size(48.dp)
        .clickable(onClick = { /* action */ }),
    contentAlignment = Alignment.Center
) {
    Icon(
        imageVector = Icons.Default.Info,
        contentDescription = "More information",
        modifier = Modifier.size(24.dp)
    )
}

Grouping and Merging Semantics

// Merge child semantics into a single announcement
Row(
    modifier = Modifier
        .clearAndSetSemantics {
            contentDescription = "Alice Johnson, Software Engineer, Online"
        }
        .clickable { onUserClick() }
) {
    Avatar(url = user.avatarUrl)
    Column {
        Text(user.name)
        Text(user.title)
    }
    OnlineIndicator(isOnline = user.isOnline)
}

// Role assignment
Box(
    modifier = Modifier
        .clickable { onAction() }
        .semantics { role = Role.Button }
) {
    Text("Custom Button")
}

// Tab role
Text(
    text = tabTitle,
    modifier = Modifier
        .clickable { onTabSelect() }
        .semantics {
            role = Role.Tab
            selected = isSelected
        }
)

Toggle and Switch Accessibility

Row(
    modifier = Modifier
        .toggleable(
            value = isEnabled,
            onValueChange = { onToggle(it) },
            role = Role.Switch
        )
        .padding(16.dp)
        .semantics {
            contentDescription = "Dark mode"
            stateDescription = if (isEnabled) "Enabled" else "Disabled"
        }
) {
    Text("Dark Mode", modifier = Modifier.weight(1f))
    Switch(checked = isEnabled, onCheckedChange = null) // null: Row handles toggle
}

Traversal Order

Column(
    modifier = Modifier.semantics {
        traversalIndex = 0f // read first
    }
) {
    Text("Important announcement")
}

Column(
    modifier = Modifier.semantics {
        traversalIndex = 1f // read second
    }
) {
    Text("Secondary content")
}

iOS / SwiftUI

Accessibility Labels and Hints

Image(systemName: "gear")
    .accessibilityLabel("Settings")
    .accessibilityHint("Opens application settings")

Button(action: { deleteItem() }) {
    Image(systemName: "trash")
}
.accessibilityLabel("Delete item")
.accessibilityHint("Permanently removes this item from your list")

Combining Child Elements

HStack {
    Image(systemName: "person.circle")
    VStack(alignment: .leading) {
        Text(user.name)
        Text(user.role)
    }
}
.accessibilityElement(children: .combine) // reads as single element

// Ignore children, provide custom label
HStack {
    StarRating(rating: 4.5)
}
.accessibilityElement(children: .ignore)
.accessibilityLabel("Rating: 4.5 out of 5 stars")

Dynamic Type Support

// Use system text styles that scale automatically
Text("Heading")
    .font(.title)

Text("Body text")
    .font(.body)

// Custom sizes that scale
@ScaledMetric(relativeTo: .body) var iconSize: CGFloat = 24

Image(systemName: "star.fill")
    .frame(width: iconSize, height: iconSize)

// Fixed minimum line height
Text("Important text")
    .font(.body)
    .lineSpacing(4)

Reduce Motion Support

@Environment(\.accessibilityReduceMotion) var reduceMotion

var animation: Animation? {
    reduceMotion ? nil : .spring(response: 0.5)
}

Button("Animate") {
    withAnimation(animation) {
        isExpanded.toggle()
    }
}

VoiceOver Rotor Support

List(items) { item in
    ItemRow(item: item)
        .accessibilityRotorEntry(id: item.id, in: .headings)
}

// Custom rotor
.accessibilityRotor("Unread Messages") {
    ForEach(messages.filter(\.isUnread)) { message in
        AccessibilityRotorEntry(message.subject, id: message.id)
    }
}

Accessibility Actions

ItemRow(item: item)
    .accessibilityAction(named: "Delete") {
        deleteItem(item)
    }
    .accessibilityAction(named: "Mark as favorite") {
        toggleFavorite(item)
    }

Cross-Platform Patterns

Color Contrast Requirements (WCAG 2.1)

Minimum contrast ratios:
- Normal text:     4.5:1 (AA)
- Large text:      3.0:1 (AA, >=18sp or >=14sp bold)
- UI components:   3.0:1 (AA)
- Enhanced (AAA):  7.0:1 for normal text, 4.5:1 for large text

Common passing combinations:
- #000000 on #FFFFFF -> 21:1 (pass all)
- #595959 on #FFFFFF -> 7.0:1 (pass AAA)
- #767676 on #FFFFFF -> 4.54:1 (pass AA)
- #949494 on #FFFFFF -> 2.95:1 (FAIL AA)

Focus Management

// Android/Compose: Request focus
val focusRequester = remember { FocusRequester() }

TextField(
    value = text,
    onValueChange = { text = it },
    modifier = Modifier.focusRequester(focusRequester)
)

LaunchedEffect(Unit) {
    focusRequester.requestFocus()
}
// iOS/SwiftUI: Focus state
@FocusState private var isTextFieldFocused: Bool

TextField("Search", text: $query)
    .focused($isTextFieldFocused)

Button("Focus Search") {
    isTextFieldFocused = true
}

Error Announcements

// Android: Announce error to screen reader
Text(
    text = "Invalid email address",
    color = MaterialTheme.colorScheme.error,
    modifier = Modifier.semantics {
        liveRegion = LiveRegionMode.Assertive
        error("Invalid email address")
    }
)
// iOS: Post accessibility notification
AccessibilityNotification.Announcement("Invalid email address")
    .post()

Testing Accessibility

Android

@Test
fun profileCard_hasCorrectSemantics() {
    composeTestRule.setContent {
        ProfileCard(user = testUser)
    }

    composeTestRule
        .onNodeWithContentDescription("User profile photo")
        .assertExists()

    composeTestRule
        .onNode(hasClickAction() and hasText("View Profile"))
        .assert(hasMinimumTouchTargetSize())
}

Manual Testing Checklist

1. Enable TalkBack (Android) / VoiceOver (iOS)
2. Navigate through the entire screen using swipe gestures
3. Verify every interactive element is reachable and announced
4. Check that images have meaningful descriptions (or are hidden if decorative)
5. Verify touch targets are at least 48dp x 48dp
6. Test with font scaling at 200%
7. Test with display size set to largest
8. Test with high contrast / bold text enabled
9. Verify color is not the only indicator of state
10. Check focus order matches visual reading order

Best Practices

  • Every interactive element must have a content description or accessible label.
  • Never rely solely on color to convey meaning; pair with icons, text, or patterns.
  • Group related elements so screen readers announce them as a single unit.
  • Use heading semantics to create a navigable document structure.
  • Test with real assistive technology, not just automated checks.
  • Support both larger text sizes and reduced motion preferences.
  • Provide live region announcements for dynamic content changes (toasts, counters, errors).
  • Minimum 48dp (Android) / 44pt (iOS) touch target for all interactive elements.