mobile-principles について
モバイル Web、iOS、Android向けのタッチ優先設計スキルです。Claude Code、claude.ai、Coworkで、タップ領域、ジェスチャー、安全領域、アクセシビリティ、性能の指針を参照できます。
主な機能
- 01タップ領域のサイズと間隔
- 02タッチ操作とホバーのルール
- 03親指が届く範囲のレイアウト
- 04安全領域とシステム余白への対応
- 05クロスプラットフォームの動きを抑える方法
- 06ジェスチャーと性能の指針
活用シーン
- 01モバイル画面のタッチ操作を確認
- 02ノッチやシステムバーを考慮した配置
- 03アニメーションに動きを抑える設定を追加
- 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 / iPadOS | 44x44 pt | 28x28 pt | ベゼル付きコントロールの周囲に約12 pt、ベゼルなしのコントロールの周囲に約24 ptのパディング | Apple HIG、アクセシビリティ > モビリティ |
| Apple watchOS | 44x44 pt | 28x28 pt | 上記と同じ | Apple HIG |
| Android | 48x48 dp | 48x48 dp | ターゲット間に8 dp | Material Design 3 |
| Web モバイル | 44x44 CSS px | 24x24 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-top | right |
| SwiftUI | .safeAreaInset(edge: ...)、safeAreaInsets env | ナビゲーションバー、タブバー、ノッチ、ホーム |
| Compose | Modifier.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 JS | window.matchMedia('(prefers-reduced-motion: reduce)') |
| SwiftUI | @Environment(\.accessibilityReduceMotion) var reduceMotion |
| UIKit | UIAccessibility.isReduceMotionEnabled |
| Compose | Settings.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 |
参考資料
- Steven Hoober、「ユーザーはモバイルデバイスを実際にどのように持つのか?」、UXmatters、2013年2月18日(観察対象n=1,333:片手持ち49%、手のひらに載せる持ち方36%、両手持ち15%):https://www.uxmatters.com/mt/archives/2013/02/how-do-users-really-hold-mobile-devices.php
- Apple Human Interface Guidelines(iOS):https://developer.apple.com/design/human-interface-guidelines/
- Material Design(Android):https://m3.material.io/foundations/layout/canonical-layouts/overview
- WCAG 2.5.8 ターゲットサイズ(最小)、レベルAA、24x24 CSS px:https://www.w3.org/WAI/WCAG22/Understanding/target-size-minimum.html
- WCAG 2.5.5 ターゲットサイズ(拡張)、レベルAAA、44x44 CSS px:https://www.w3.org/WAI/WCAG22/Understanding/target-size-enhanced.html