Mobile-specific UX principles - touch targets, hover-less doctrine, thumb zones, safe areas, gestures, mobile perf budgets. Cross-platform (web mobile, iOS, Android).
npx skills add https://github.com/AThevon/genjutsu --skill mobile-principles
> Touch-first UX context. Loaded when mobile is detected (web mobile, iOS, Android).
> Concise rules here. Deep-dive in references/.
| Platform | Minimum | Recommended | Spec |
|---|---|---|---|
| Apple iOS | 44pt | 44pt + 8pt spacing | Apple HIG |
| Android | 48dp | 48dp + 8dp spacing | Material Design |
| Web mobile | 44px | 44px + 8px spacing | WCAG 2.5.5 |
Rule of thumb: any tap target smaller than the platform minimum is a usability bug, period.
The hit area can extend beyond the visible glyph (use padding, hitSlop, or a transparent inner spacer), but the *interactive* surface must reach the minimum. Spacing matters as much as size: two 44pt buttons touching edges are still mistappable.
:hover does not exist on touch. Treating it as a primary trigger means hidden affordances on every phone. Anything reachable only by hover is, on mobile, simply gone. Visible-by-default is the rule; hover styles are a desktop *enhancement*, never a load-bearing interaction.
CSS - gate hover styles behind a media query:
.card { opacity: 1; transform: translateY(0); }
@media (hover: hover) and (pointer: fine) {
.card { opacity: 0.85; }
.card:hover { opacity: 1; transform: translateY(-2px); }
}
SwiftUI - tap and long-press, no pseudo-hover:
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 for tap + long-press:
Box(
modifier = Modifier
.combinedClickable(
onClick = { toggleLike() },
onLongClick = { showContextMenu() },
)
) {
Icon(Icons.Default.Favorite, contentDescription = "Like")
}
Steven Hoober's research shows portrait phone use is overwhelmingly one-handed or cradled, with the thumb pivoting from the bottom corner. The screen splits into reachable zones:
+------+----+------+
| HARD | OK | HARD | <- top: stretch, two-handed only
+------+----+------+
| OK | OK | OK | <- middle: comfortable
+------+----+------+
| EASY |EASY| EASY | <- bottom: natural thumb arc
+------+----+------+
Rule: primary CTA goes in the bottom half. Secondary, less-frequent or destructive actions go in the top. Never put a "Pay" button in the top-right corner of a phone screen.
| Platform | API | Insets respected |
|---|---|---|
| Web | env(safe-area-inset-top|right|bottom|left) + viewport-fit=cover | notch, home indicator |
| SwiftUI | .safeAreaInset(edge: ...), safeAreaInsets env | nav bar, tab bar, notch, home |
| Compose | Modifier.windowInsetsPadding(WindowInsets.safeDrawing) | system bars, IME, cutouts |
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 */ }
| Platform | 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 | Custom helper using Settings.Global.ANIMATOR_DURATION_SCALE (see code below; deep-dive in 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 (helper pattern):
@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)
> Note: prefer ValueAnimator.areAnimatorsEnabled() (API 26+) - it returns false when the animator duration scale is 0, which the developer-options "Animation off" toggle, Battery Saver, and the user-facing "Remove animations" (Settings -> Accessibility) toggle all set. Deep dive in references/accessibility-mobile.md.
The five gestures users already know. Reusing them is free UX; reinventing them is friction.
WorkManager on Android, BGTaskScheduler on iOS), avoid wake-locks unless the user explicitly asked for foreground media.Save-Data request header and navigator.connection.saveData. iOS: URLSessionConfiguration.allowsCellularAccess and NWPathMonitor for cellular vs Wi-Fi. Android: ConnectivityManager + NetworkCapabilities to detect metered networks./* 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; }
}
// BAD - 32dp icon button, mistappable, fails Material guideline
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),
)
}
// 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()
}
| Need | Load |
|---|---|
| Gesture deep-dive | references/gestures-deep.md |
| Mobile a11y deep-dive | references/accessibility-mobile.md |
| Compose-specific anim | ../compose-motion/SKILL.md |
| SwiftUI-specific anim | ../swiftui-motion/SKILL.md |
Integration with protocols.io API for managing scientific protocols. This skill should be used when working with protocols.io to search, create, update, or publish protocols; manage protocol steps and materials; handle discussions and comments; organize workspaces; upload and manage files; or integrate protocols.io functionality into workflows. Applicable for protocol discovery, collaborative protocol development, experiment tracking, lab protocol management, and scientific documentation.
Analyzes job descriptions and generates tailored resumes that highlight relevant experience, skills, and achievements to maximize interview chances
Generate Excalidraw diagrams from natural language descriptions. Use when asked to "create a diagram", "make a flowchart", "visualize a process", "draw a system architecture", "create a mind map", or "generate an Excalidraw file". Supports flowcharts, relationship diagrams, mind maps, and system architecture diagrams. Outputs .excalidraw JSON files that can be opened directly in Excalidraw.
Build and distribute Expo development clients locally or via TestFlight
Use when you have a written implementation plan to execute in a separate session with review checkpoints
Data structure for annotated matrices in single-cell analysis. Use when working with .h5ad files or integrating with the scverse ecosystem. This is the data format skill—for analysis workflows use scanpy; for probabilistic models use scvi-tools; for population-scale queries use cellxgene-census.
Benchling R&D platform integration. Access registry (DNA, proteins), inventory, ELN entries, workflows via API, build Benchling Apps, query Data Warehouse, for lab data management automation.
Comprehensive molecular biology toolkit. Use for sequence manipulation, file parsing (FASTA/GenBank/PDB), phylogenetics, and programmatic NCBI/PubMed access (Bio.Entrez). Best for batch processing, custom bioinformatics pipelines, BLAST automation. For quick lookups use gget; for multi-service integration use bioservices.
Take athevon/mobile-principles from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.