mcpField
M
mobile-principles

mobile-principles

github.com/athevonデザイン・UI698970 回インストール3 週間前に更新
リポジトリ

mobile-principles について

モバイル Web、iOS、Android向けのタッチ優先設計スキルです。Claude Code、claude.ai、Coworkで、タップ領域、ジェスチャー、安全領域、アクセシビリティ、性能の指針を参照できます。

主な機能

  1. 01タップ領域のサイズと間隔
  2. 02タッチ操作とホバーのルール
  3. 03親指が届く範囲のレイアウト
  4. 04安全領域とシステム余白への対応
  5. 05クロスプラットフォームの動きを抑える方法
  6. 06ジェスチャーと性能の指針

活用シーン

  1. 01モバイル画面のタッチ操作を確認
  2. 02ノッチやシステムバーを考慮した配置
  3. 03アニメーションに動きを抑える設定を追加
  4. 04一般的なモバイルジェスチャーを選択

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/athevon/genjutsu --skill mobile-principles

インストール後、Claude Code で/mobile-principlesを入力して読み込まれたか確認します。

詳細

バージョン依存の情報です。 以下に記載されているすべての API 名、SDK の要件、ブラウザーサポートに関する主張は、主要な情報源に基づき 2026-09-08 に確認済みです。何をいつ確認したかは _jutsu/VERSIONS.md に記載されています。この日付が古い場合は、バージョン番号に基づいて作業する前に再確認してください。

モバイルの原則

タッチ操作を優先する UX のコンテキストです。モバイル(Web モバイル、iOS、Android)が検出されたときに読み込まれます。 ここでは簡潔なルールを示します。詳しくは references/ を参照してください。


タッチターゲット

プラットフォーム推奨値(これを基準に設計)最小値間隔仕様
Apple iOS / iPadOS44x44 pt28x28 ptベゼル付きコントロールの周囲に約12 pt、ベゼルなしのコントロールの周囲に約24 ptのパディングApple HIG、アクセシビリティ > モビリティ
Apple watchOS44x44 pt28x28 pt上記と同じApple HIG
Android48x48 dp48x48 dpターゲット間に8 dpMaterial Design 3
Web モバイル44x44 CSS px24x24 CSS px重ならない24 pxの間隔があればAAの例外を満たしますWCAG 2.5.5 ターゲットサイズ(拡張)、AAA = 44x44;WCAG 2.5.8 ターゲットサイズ(最小)、AA = 24x24

経験則: プラットフォームの最小値より小さいタップターゲットは、例外なくユーザビリティ上の不具合です。 ヒット領域は表示されているグリフより広くできます(パディング、hitSlop、または透明な内側のスペーサーを使用します)が、操作可能な領域は最小値を満たす必要があります。間隔もサイズと同じくらい重要です。44ptのボタンが2つ、端同士で接していると、誤タップが起きやすいままです。


ホバーなしの原則

:hover はタッチ操作には存在しません。これを主要なトリガーとして扱うと、すべてのスマートフォンで操作の手がかりが隠れてしまいます。ホバーでしかアクセスできないものは、モバイルでは存在しないのと同じです。常に表示することが原則です。ホバースタイルはデスクトップ向けの拡張機能であり、操作に不可欠なものにしてはいけません。

CSS - メディアクエリーを使ってホバースタイルを制限します:

.card { opacity: 1; transform: translateY(0); }

@media (hover: hover) and (pointer: fine) {
  .card { opacity: 0.85; }
  .card:hover { opacity: 1; transform: translateY(-2px); }
}

SwiftUI - タップと長押しを使い、疑似ホバーは使いません:

Image(systemName: "heart")
  .onTapGesture { toggleLike() }
  .contextMenu {
    Button("Share", systemImage: "square.and.arrow.up", action: share)
    Button("Report", systemImage: "flag", role: .destructive, action: report)
  }

Compose - タップと長押しには combinedClickable を使います:

Box(
  modifier = Modifier
    .combinedClickable(
      onClick = { toggleLike() },
      onLongClick = { showContextMenu() },
    )
) {
  Icon(Icons.Default.Favorite, contentDescription = "Like")
}

親指の操作範囲(Hoober)

Steven Hoober の調査によると、縦向きのスマートフォンは、ほとんどの場合、片手で持つか、手のひらに包むように持たれ、親指は下隅を支点として動きます。画面は次のように、手が届く範囲に分かれます。

+------+----+------+
| HARD | OK | HARD |   <- top: stretch, two-handed only
+------+----+------+
|  OK  | OK |  OK  |   <- middle: comfortable
+------+----+------+
| EASY |EASY| EASY |   <- bottom: natural thumb arc
+------+----+------+
  • 下部3分の1(EASY): 主要な CTA、送信、確認、FAB、タブバー。
  • 中央(OK): コンテンツ、副次的な操作。
  • 上部(HARD): 戻る、閉じる、検索、プロフィールなど、反射的に押すのではなく、手を伸ばして操作するとユーザーが想定するもの。

ルール: 主要な CTA は画面の下半分に配置します。副次的な操作、使用頻度の低い操作、破壊的な操作は画面の上部に配置します。スマートフォンの画面右上に「支払い」ボタンを置いてはいけません。


セーフエリア

プラットフォームAPI対応するインセット
Web`env(safe-area-inset-topright
SwiftUI.safeAreaInset(edge: ...)、safeAreaInsets envナビゲーションバー、タブバー、ノッチ、ホーム
ComposeModifier.windowInsetsPadding(WindowInsets.safeDrawing)システムバー、IME、カットアウト

Web:

<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
.fab {
  position: fixed;
  bottom: calc(env(safe-area-inset-bottom) + 16px);
  right: calc(env(safe-area-inset-right) + 16px);
}

SwiftUI:

ScrollView { content }
  .safeAreaInset(edge: .bottom) {
    PrimaryCTA().padding()
  }

Compose:

Column(
  modifier = Modifier
    .fillMaxSize()
    .windowInsetsPadding(WindowInsets.safeDrawing)
) { /* content */ }

モーションの軽減(クロスプラットフォーム共通)

プラットフォームAPI
Web CSS@media (prefers-reduced-motion: reduce)
Web JSwindow.matchMedia('(prefers-reduced-motion: reduce)')
SwiftUI@Environment(\.accessibilityReduceMotion) var reduceMotion
UIKitUIAccessibility.isReduceMotionEnabled
ComposeSettings.Global.ANIMATOR_DURATION_SCALE を使用するカスタムヘルパー(下記のコードを参照。詳しくは references/accessibility-mobile.md を参照)

SwiftUI:

struct Hero: View {
  @Environment(\.accessibilityReduceMotion) var reduceMotion
  @State private var shown = false

  var body: some View {
    Text("Welcome")
      .opacity(shown ? 1 : 0)
      .offset(y: shown ? 0 : (reduceMotion ? 0 : 20))
      .animation(reduceMotion ? .none : .easeOut(duration: 0.3), value: shown)
      .onAppear { shown = true }
  }
}

UIKit:

let duration = UIAccessibility.isReduceMotionEnabled ? 0 : 0.3
UIView.animate(withDuration: duration) {
  view.alpha = 1
  view.transform = .identity
}

Compose(ヘルパーパターン):

@Composable
fun rememberReduceMotion(): Boolean {
  val context = LocalContext.current
  return remember {
    Settings.Global.getFloat(
      context.contentResolver,
      Settings.Global.ANIMATOR_DURATION_SCALE,
      1f,
    ) == 0f
  }
}

val reduceMotion = rememberReduceMotion()
val spec = if (reduceMotion) snap() else tween<Float>(durationMillis = 300)

注:ValueAnimator.areAnimatorsEnabled()(API 26以降)を優先してください。アニメーターの継続時間スケールが0の場合に false を返します。アニメーターの継続時間スケールは、開発者向けオプションの「アニメーションをオフ」、省電力モード、ユーザー向けの「アニメーションを削除」(設定 -> アクセシビリティ)の各切り替えによって0に設定されます。詳しくは references/accessibility-mobile.md を参照してください。

モバイルジェスチャー(標準的なパターン)

ユーザーがすでに知っている5つのジェスチャーです。再利用すればUXの手間が省けますが、独自に作り直すと操作の妨げになります。

  • スワイプで戻る: iOSでは画面の端からスワイプしてナビゲーションスタックを戻ります。決して上書きしないでください。Androidでは予測型バックで同様に動作させます。予測型バックは Android 13 (API 33) から利用でき、android:enableOnBackInvokedCallback="true" と OnBackPressedCallback/PredictiveBackHandler を使って有効にします。Android 13-14ではアニメーションは開発者向けオプションの中にありましたが、Android 15 以降は、オプトインしたアプリでシステムのホームへのバック/アクティビティ間/タスク間アニメーションが自動的に表示されます。
  • プルして更新: スクロール領域の先頭で下方向にドラッグして再取得します。フィード、メール、リストで標準的な操作です。
  • ドラッグして閉じる: モーダルシートや画像ビューアは、しきい値(通常は100~150pt)を超えて下方向にドラッグすると閉じます。
  • ピンチしてズーム: 画像、地図、ズーム可能なキャンバスで、2本指を広げたりつまんだりします。最小/最大倍率を守ってください。
  • 行のスワイプアクション: リストの行を横方向にスワイプすると、コンテキストアクション(削除、アーカイブ、既読にする)が表示されます。先頭側と末尾側のスワイプで異なるアクションセットを割り当てます。

モバイルのパフォーマンス予算

  • コールドスタート: ミッドレンジ端末で2秒未満を目標にします。基準端末:Android Pixel 4a、iPhone SE(第2世代)。Pixel 4aでアプリの起動に4秒かかるなら、ユーザーが実際に使っているローエンド端末では8秒かかります。
  • フレーム予算: 60fpsでは1フレームあたり16.67ms、120fpsでは8.33ms(ProMotion/高リフレッシュレートのAndroid)です。メインスレッドでこれを超えて同期処理を行うと、動作がカクつきます。
  • バイナリサイズ: 大容量のメディアライブラリを追加する前に、APKは30MB未満、IPAは50MB未満を目標にします。Lottie/Riveは500KB~2MB追加されます。アセットフォルダーに注意してください。PNGがWebPやベクター形式より容量を増やす一般的な原因です。
  • バッテリー: バックグラウンドでCPUを継続的に動作させないでください。処理をまとめ、プラットフォームのスケジューラー(AndroidではWorkManager、iOSではBGTaskScheduler)を使い、ユーザーがフォアグラウンドでのメディア再生を明示的に求めた場合を除き、ウェイクロックは避けてください。
  • ネットワーク: 接続状況のヒントを尊重してください。WebではSave-Dataリクエストヘッダーとnavigator.connection.saveDataを使います。iOSではURLSessionConfiguration.allowsCellularAccessとNWPathMonitorを使って、モバイル通信とWi-Fiを判別します。AndroidではConnectivityManagerとNetworkCapabilitiesを使って、従量制ネットワークを検出します。

アンチパターン(BAD/GOOD)

1. ホバー時のみ表示
/* BAD - on mobile the action button literally never appears */
.card .actions { opacity: 0; }
.card:hover .actions { opacity: 1; }
/* GOOD - visible by default, hover is a desktop enhancement */
.card .actions { opacity: 1; }

@media (hover: hover) and (pointer: fine) {
  .card .actions { opacity: 0; transition: opacity 0.15s ease-out; }
  .card:hover .actions { opacity: 1; }
}
2. 最小サイズ未満のタッチターゲット
// BAD - looks 32dp and reads as a 32dp target to whoever writes the next one.
// Material 3's IconButton actually applies minimumInteractiveComponentSize() itself, so
// the touch target stays 48dp here - but only because the component rescues you, and only
// while LocalMinimumInteractiveComponentEnforcement is on. On a custom Box or Row with the
// same modifier, the target really is 32dp and really is mistappable.
IconButton(
  onClick = onDelete,
  modifier = Modifier.size(32.dp),
) { Icon(Icons.Default.Delete, contentDescription = "Delete") }
// GOOD - 48dp minimum even when the visible icon is 24dp
IconButton(
  onClick = onDelete,
  modifier = Modifier.size(48.dp),
) {
  Icon(
    Icons.Default.Delete,
    contentDescription = "Delete",
    modifier = Modifier.size(24.dp),
  )
}
3. セーフエリアのインセットを無視
// BAD - the CTA sits under the home indicator on every modern iPhone
VStack {
  Spacer()
  Button("Continue", action: next)
    .frame(maxWidth: .infinity)
    .padding()
}
// GOOD - safeAreaInset keeps the button reachable and visible
ScrollView { content }
  .safeAreaInset(edge: .bottom) {
    Button("Continue", action: next)
      .frame(maxWidth: .infinity)
      .padding()
  }

クイックリファレンス:サブスキルの読み込み

必要なもの読み込むもの
ジェスチャーの詳細references/gestures-deep.md
モバイルのアクセシビリティの詳細references/accessibility-mobile.md
Compose固有のアニメーション../compose-motion/SKILL.md
SwiftUI固有のアニメーション../swiftui-motion/SKILL.md

参考資料

コメント