Upgrade to Pro — share decks privately, control downloads, hide ads and more …

AI に Inclusive UI を書かせよう — Design Rules Skill で...

AI に Inclusive UI を書かせよう — Design Rules Skill で Compose UI を作り直す

DroidKaigi 2026 9/3(Day3) 11:20 ~ 12:00 Narwhalのセッション
https://2026.droidkaigi.jp/timetable/1233663/

More Decks by テオリア・テクノロジーズ

Other Decks in Programming

Transcript

  1. AI に Inclusive UI を書かせよう — Design Rules Skill で

    Compose UI を作り直す bami / テオリア‧テクノロジーズ DroidKaigi 2026
  2. Before / 今 After / 聞き終わったら AI に、 ルールを渡さずに UI

    を 書かせている AI に、 ルールを渡して、やさしい UI を 書かせている 8
  3. アジェンダ A. 問題の所在 — 「それっぽい」は、「やさしい」ではない B. 経緯と背景 — 『認知症の⼈にもやさしいデザインの⼿引き』と輪読会 C.

    Skill 設計 — AI に読ませるルールの作り⽅ D. Compose 実装 — 実コードと録画デモ E. Skill を育てるループ — ⼿動‧⾃動‧CI で確かめて、ルールを強くする F. まとめ 9
  4. ただし"それっぽい"は"保証"されていない ① 未検証 — "綺麗に⾒える"は"読める"の保証ではない。 読める下限を WCAG が 4.5:1 と定めているが、AI

    は計算して確かめない ② 運任せ — 同じ依頼でもプロンプト‧モデルの引きで揺れる WCAG 2.2 コントラスト最低限(1.4.3)∕ 実測でも semantics を保つ⽣成と落とす⽣成があった 11
  5. 「うちのアプリには関係ない」と思いますか? 認知症という取りこぼされがちな起点から設計する = Inclusive Design その原理が Universal Design 限定的な⼊⼝の⼯夫が、全ユーザーに広がる Inclusive

    Design(⽅法) → Inclusive UI(成果物) ⽀える原理 = Universal Design Inclusive Design Principles(7原則‧⽇本語訳あり)∕ Universal Design=Ronald Mace「⼀つの設計で全員に」の原理(7原則) 20
  6. ⼿引きの基本:2つの考え⽅の上に、5つの視点 ① 記憶に頼らず⾏動できる空間づくり — その場の⼿がかりで分かる ② 安⼼して⾃分で選べる居場所づくり — 迷わず、間違えても戻れる ⼿引きの5視点

    アプリでの配慮 A ⾊‧明度 コントラスト B サイン ⽂字併記‧ことば C 明るさ テーマ∕ダークモード追従 D 親しみ‧安⼼ 現在地‧戻れる安⼼ E 安全な屋外 危険な操作からさりげなく守る(破壊的操作の確認) 『認知症の⼈にもやさしいデザインの⼿引き』第3章「デザインの基本的な考え⽅」「5つの視点」(P.6〜) 21
  7. 公式ガイドも Skill も、あるにはある 公式ガイド(Material Design 3‧Android Accessibility)はある =AI も Android

    Knowledge Base 経由で引ける 公式 Skill もある(android/skills) =ただしアクセシビリティを主題にした Skill は無い Google ⽅針「docs にあることは Skill 化しない」 =我々が⾜す知識は docs に無い Material Design 3∕Android Accessibility ガイド(developer.android.com)∕android/skills(2026-08 v1.0.7 実確認) ∕Google philosophy「Built for deprecation」2026-08 27
  8. 公開されている AI 向けリソースを調べた(2026年8⽉) リソース Compose 固有 認知負荷 実機検証 Web 向け

    a11y lint (eslint-plugin-jsx-a11y 等) ✕ — ✕ material-3-skill ◎ ✕ ✕ aldefy / Meet-Miyani ◦ ✕ ✕ inclusive-design-skills ✕ ◎ audit 我々 ◎ ◎ ◎ 出典 eslint-plugin-jsx-a11y(Web 向け a11y lint): https://github.com/jsx-eslint/eslint-plugin-jsx-a11y material-3-skill(Ivan Morgillo): https://github.com/hamen/material-3-skill compose-skill(aldefy): https://github.com/aldefy/compose-skill compose-skill(Meet-Miyani): https://github.com/Meet-Miyani/compose-skill inclusive-design-skills(Owl-Listener): https://github.com/Owl-Listener/inclusive-design-skills WCAG=公式の国際アクセシビリティ基準(個⼈ OSS とは権威レベルが異なる) 28
  9. いまの Skill も、DESIGN.md も、捨てない 役割 内容 担い⼿ ⾜す 認知負荷×Compose(コントラスト∕テーマ∕ナビ ∕⽂⾔∕エラー∕ピクト∕48dp)

    我々 借りる Material Design の⾒た⽬(トークン‧配⾊‧コン ポーネント) material-3-skill 乗る Compose の⼟台(XML移⾏‧ナビ‧テスト環境) android/skills 委譲 ブランドテーマなどの意匠 DESIGN.md 順位:inclusive の [must](=必ず守る⼀線) > Figma‧DESIGN.md 忠実 > 余計な装飾を⾜さない 31
  10. Skill の全体像 inclusive-compose-skill/ ── 自分で書く ── ├── .rulesync/ │ ├──

    rules/ │ └── skills/inclusive-compose/ ├── rulesync.jsonc ├── verification.md ├── .github/workflows/verify.yml └── README.md・LICENSE ← Skill の単一ソース(ここを直す) overview + 14カテゴリ SKILL.md = いつ使うか 生成の設定 検証手順 CI(ドリフト検知+connectedCheck) (通常のリポファイル) ── npx rulesync@16 generate(生成物・触らない・delete:true)── ├── CLAUDE.md Claude Code の入口 ├── .claude/ rules / skills ├── .cursor/ rules / skills └── .github/ copilot-instructions / skills 別リポ(借りる)── material-3-skill … Material Design 3 の見た目 32
  11. must と nice-to-have で階層化 # 情報の量(手引き:記憶に頼らせない) ## must - 1画面1タスクを基本にする。関係ない情報を同時に出さない

    - 選択肢は折り返して全文を見せる(切らない) ## nice - 段階的開示(必要になったら追加情報を出す) 35
  12. 実物:ルール本体 # 情報の量(手引き:記憶に頼らせない) ## must / ## nice ## Compose

    Text(choiceLabel) // maxLines / Ellipsis で切らない=全文が折り返る var expanded by rememberSaveable { mutableStateOf(false) } if (expanded) Text(detail) // 必要になってから追加情報を出す ## 検証 フォントスケール 200% で全文が見切れないか確認 36
  13. 深掘り①コントラスト|Compose ではこう書く [must] 文字・重要 UI と背景は 4.5:1 以上(大きな文字・太字は 3:1) [must]

    色は ColorScheme に集約 (画面ごとにハードコードしない・アプリルート=画面間で一貫) [must] 主役は 4.5:1 を検証して固定 (標準の配色ペア〔primary/onPrimary 等〕はトーン差50+で 4.5:1 を狙う 設計・公式の数値保証は無い/カスタム色・非対ロール・outline は保証外) [nice] Theme Builder の contrast level(medium / high)を使う 38
  14. 深掘り③|⼿引き由来とUI慣習を分けて書く [must] アイコンには文字を併記する ← 手引き由来 [must] 色・文字・アイコンの三重で状態を伝える ← 手引き由来 [nice]

    赤=危険 / 緑=完了 / 青=情報 ← 一般的なUI慣習・補助 // Compose: Icon(contentDescription) + 隣に Text ラベル // 状態は色以外(テキスト・形)でも必ず示す 42
  15. ルールが良くても、description が曖昧だと呼ばれない ルールに frontmatter(いつ使うか)を付けたもの=Skill description の⼀覧だけが常駐‧本⽂は使う時だけ読み込まれる 悪い例「Inclusive UI のルール集」→ 効く例「Compose

    で UI を書く‧直す時に使 う」 name: inclusive-compose description: "… Compose の UI を書く・直すときは必ず参照する。" targets: ["*"] user-invocable: true claudecode: { paths: ["**/*.kt", "**/*.kts"] } 44
  16. 1ソースから、rulesync が3ツールに配る ルール本体 + いつ使うかの設定 .rulesync/ ↓ skill層 ← 正はここだけ

    npx rulesync@16 generate SKILL.md(標準) rules層 各ツール固有 ↓ Claude Code / Cursor / GitHub Copilot 正は .rulesync の1か所‧rulesync が skill層+rules層を3ツール分⽣成=1か所直せば全部追従 (⾃分で書かず OSS) 受け取る側は SKILL.md の skill層+各ツール固有の rules層の2層 3形式は"差別化"でなく「多くの⼈が使えるように」の配布形態 rulesync(dyoshikawa): https://github.com/dyoshikawa/rulesync 45
  17. ソースと⽣成物がズレたら、CI が落とす npx --yes rulesync@16 generate --check # 再生成して差分が出れば CI

    が落ちる 1か所直せば全部に伝わるという仕組みを、CI が守る 49
  18. naive のコード:選択が semantics に出ない Box(Modifier.clip(shape).background(bg).border(...) .selectableCardClick(onClick)) { Text(text, sc-camel-font-size =

    20.sp) } // sc-camel-selectable-card-click = clickable{...} = 生の clickable // role も選択状態も semantics に出ない 58
  19. skill のコード:selectable+role+group+48dp Row(Modifier .selectable(selected, role = Role.RadioButton, sc-camel-on-click = onClick)

    .heightIn(min = 48.dp) // タップ48dp ) { Text(text); if (selected) Icon(CheckCircle, sc-camel-content-description = null) } // 親 Column に Modifier.selectableGroup() // 選択チェックは"色以外"の手がかり 59
  20. 実描画:fontScale 2.0 で残る課題は、⽂字ではなく配置 naive(fontScale 2.0) skill(fontScale 2.0) 拡⼤追従は原則(最⼩16sp‧折返し‧固定 ⾼さ禁⽌)を守れば Compose

    が担保 = Skill の差別化点ではない 両⽅に共通の窮屈さ: 帯が本⽂に重なる ‧ナビラベルが切れる 200% は等倍のデザインにもコードにも写 らない → Skill の検証ルール =fontScale 2.0 のスクショテストで描画 して確認 64
  21. skill のコード:WindowSizeClass で組み替える val expanded = WindowSizeClass.calculateFromSize(DpSize(maxWidth, maxHeight)) .widthSizeClass ==

    WindowWidthSizeClass.Expanded if (expanded) Row else { … } // ≥840dp:左右2ペイン・行長を保つ Column { … } // 1列。順序は同じ skill は画⾯幅を WindowSizeClass で⾒て、広いとき(Expanded‧≥840dp)だけ 1列→2ペインに組み替える(引き伸ばさず⾏⻑を保つ‧順序は変えない) naive にこの分岐は無い=そのまま横に伸びる 67
  22. skill:寄り添う⽂⾔+出た瞬間に読み上げ、消さない if (showNameRequiredError) Text( text = "お名前が未設定のため、レポートを送れません。" + "上の「当事者様のお名前」からお名前を登録してください。", color

    = MaterialTheme.colorScheme.error, modifier = Modifier.semantics { sc-camel-live-region = LiveRegionMode.Polite; error(msg) // 出た瞬間に読み上げ }, ) // 自動では消さず、名前登録まで残す ⽂⾔を「原因+次の⼀⼿」に書き換える 表⽰と読み上げを同じ⽂字列にし、error+liveRegion で TalkBack へ伝える 70
  23. ⼿動検証は3⼿段 TalkBack Font Scale 200% 読み上げの内容 ⽂字の⾒切れ Accessibility Scanner コントラスト‧タップ領域

    ‧ラベル ツールは守備範囲が違う Scanner は読み上げの中⾝までは⾒ない=naive でも「提案なし」 そこは TalkBack で聞く 75
  24. ⾃動検証:semantics をテストでアサート @Test fun 選択肢は選択状態を読み上げる() { rule.onNodeWithText("自分で電話をかけた").assertIsSelected() } @Test fun

    タッチターゲットは48dpが確保される() { rule.onNodeWithText("自分で電話をかけた").assertHeightIsAtLeast(48.dp) } @Test fun エラーにerrorセマンティクスが付く() { rule.onNode(hasText("お名前が未設定", substring = true)) .assert(SemanticsMatcher.keyIsDefined(SemanticsProperties.Error)) } 76
  25. ⾃動検証②:コントラスト‧タップ‧ラベルをまとめてチェック // 依存: androidx.compose.ui:ui-test-junit4-accessibility @Before fun enableA11yChecks() { composeTestRule.enableAccessibilityChecks() }

    // 以降のアクション実行時に ラベル欠落・低コントラスト・48dp未満 を自動 fail // 任意の時点で: composeTestRule.onRoot().tryPerformAccessibilityChecks() 77
  26. CI に組み込む+ドリフト検知 # 単一ソース(rules/) → 生成 → 差分が出たら落とす - run:

    npx --yes rulesync@16 generate --check # ドリフト検知=差分で exit 1 - run: ./gradlew connectedCheck # accessibility テストを実機/エミュで ルールとコード、両⽅の退⾏を CI が⾒張る 78
  27. 3つの持ち帰り ① Skill 設計の型 輪読会で「なぜこのルールか」を⼈の側でそろえる → 単⼀ソース → 層設計 →

    検証を Skill 本 ⽂に ② Compose 固有の Inclusive UI 実装パターン semantics / 48dp / ⽂字スケール / 戻る安⼼‧具体コードで ③ 書いて終わりにしない、育てるループ ⼿動 → ⾃動 → CI → ルール強化 82
  28. ありがとうございました 資料はこの QR から ∕ 質疑は Ask the Speaker で

    ※QRコードはデンソーウェーブの登録商標です。