From android-skills
Applies Material Design 3 UX principles for Android UI design and review, including touch targets, navigation patterns, accessibility, and an M3 compliance audit.
How this skill is triggered — by the user, by Claude, or both
Slash command
/android-skills:android-uxThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
This reference covers the non-obvious platform facts and **reviewing** an existing screen for M3 compliance, not the basics of building M3-correct UI.
This reference covers the non-obvious platform facts and reviewing an existing screen for M3 compliance, not the basics of building M3-correct UI.
Foldables introduce postures beyond window size classes. Detect them with WindowInfoTracker + FoldingFeature (Jetpack WindowManager), and never place interactive content or critical information across the hinge.
| Posture | Detection | Layout behavior |
|---|---|---|
| Flat (unfolded) | no folding feature | Treat as Medium/Expanded by width |
| Half-opened, horizontal fold (tabletop) | HALF_OPENED + Orientation.HORIZONTAL | Content top half, controls bottom half |
| Half-opened, vertical fold (book) | HALF_OPENED + Orientation.VERTICAL | List left, detail right |
| Folded (cover screen) | — | Treat as Compact |
@Composable
fun FoldAwareLayout() {
val context = LocalContext.current
val layoutInfo by WindowInfoTracker.getOrCreate(context)
.windowLayoutInfo(context) // accepts @UiContext — Activity, InputMethodService, or createWindowContext()
.collectAsStateWithLifecycle(initialValue = WindowLayoutInfo(emptyList()))
val fold = layoutInfo.displayFeatures.filterIsInstance<FoldingFeature>().firstOrNull()
when {
fold?.state == FoldingFeature.State.HALF_OPENED ->
if (fold.orientation == FoldingFeature.Orientation.HORIZONTAL) TabletopLayout() else BookLayout()
else -> StandardAdaptiveLayout() // by window size class
}
}
Compose's dynamicLightColorScheme / dynamicDarkColorScheme read the system contrast setting automatically (SDK 34+) and expose no contrast parameter. To offer in-app contrast control (Standard 0.0 / Medium 0.5 / High 1.0 tonal distance), build the scheme from Hct + SchemeContent:
// com.google.android.material:material (MDC-Android), package com.google.android.material.color.utilities
// Marked @RestrictTo(LIBRARY_GROUP) — internal API, may change between versions; re-check on version bumps.
val hct = Hct.fromInt(0xFF6750A4.toInt())
val scheme = SchemeContent(hct, /* isDark = */ false, /* contrastLevel = */ 1.0)
val colorScheme = lightColorScheme(
primary = Color(scheme.primary),
onPrimary = Color(scheme.onPrimary),
// ... map remaining roles
)
For multiplatform or a stable public API, com.materialkolor:material-color-utilities is a KMP alternative.
Pair M3's duration ladder with easing (short* → FastOutSlowIn; medium* → Emphasized(De/Ac)celerate; long*/extraLong* → Emphasized) rather than arbitrary millis. Reach for MotionScheme (Compose M3 1.4+) where available; otherwise hold durations at one source of truth in the theme.
| Token group | Range | Typical use |
|---|---|---|
short1…short4 | 50–200ms | Micro-interactions, state changes (ripple, selection, switch) |
medium1…medium4 | 250–400ms | Standard transitions (screen enter/exit, expansion, reveal) |
long1…long4 | 450–600ms | Container transforms, fade-through between large surfaces |
extraLong1…extraLong4 | 700–1000ms | Shared-element / hero transitions on tablets/foldables |
Animations must be interruptible and never block input. Respect reduced motion — Compose has no built-in LocalReducedMotion, so read ANIMATOR_DURATION_SCALE and provide one:
val LocalReducedMotion = staticCompositionLocalOf { false }
@Composable
fun AppTheme(content: @Composable () -> Unit) {
val context = LocalContext.current
val reduceMotion = remember {
Settings.Global.getFloat(context.contentResolver, Settings.Global.ANIMATOR_DURATION_SCALE, 1f) == 0f
}
CompositionLocalProvider(LocalReducedMotion provides reduceMotion) { MaterialTheme { content() } }
}
// at the call site
AnimatedVisibility(
visible = isVisible,
enter = if (LocalReducedMotion.current) EnterTransition.None else fadeIn() + slideInVertically(),
) { Content() }
(staticCompositionLocalOf + keyless remember read the setting once per Activity; for live updates switch to compositionLocalOf + a ContentObserver on ANIMATOR_DURATION_SCALE.)
Use this when reviewing a screen or feature for Material Design 3 compliance — the part that's easy to skip from memory. Score each category Pass / Partial / Fail, then fix any Partial/Fail before shipping.
MaterialTheme.colorScheme roles — no hardcoded hex/ARGB.primary for key actions, secondary/tertiary for less prominent / accents, error for errors; on* paired correctly; layered surfaces via surfaceContainerLow/High, not arbitrary grays.outline for interactive boundaries needing 3:1 (field borders, focus rings), outlineVariant for decorative dividers.MaterialTheme.typography — no inline fontSize/fontWeight.display* hero, headline* section headers, title* card/dialog titles, body* content, label* buttons/captions.MaterialTheme.shapes, not hardcoded RoundedCornerShape.extraSmall (4dp) chips, small (8dp) cards, medium (12dp) dialogs, large (16dp) sheets, extraLarge (28dp) FABs. Consistent shape language.ElevatedCard/ElevatedButton over manual shadowElevation.surfaceContainerLowest < … < Highest) — if two adjacent layers render the same color in either theme, the elevation cue is broken.androidx.compose.material3.*), no M2 (androidx.compose.material.*) and no M2/M3 mixing on a screen.FloatingActionButton for the primary action, Card for grouped content, TopAppBar for screen-level actions.Run from the project root — each hit is a category-1/3/5 violation to triage before human review:
rg --type kt 'Color\(0x[0-9a-fA-F]{6,8}\)' --files-with-matches | head # 1: hardcoded colors
rg --type kt 'RoundedCornerShape\(\s*\d+(?:\.\d+)?\s*\.dp\s*\)' --files-with-matches | head # 3: hardcoded radii
rg --type kt 'import androidx\.compose\.material\.' --files-with-matches | head # 5: Material 2 imports
selected + active indicator.Emphasized at 300–500ms; enter/exit follow M3 (fade-through, container transform); interruptible and respect reduced motion.contentDescription; contrast meets WCAG AA (4.5:1 body, 3:1 large text + UI components); touch targets ≥48dp.semantics(mergeDescendants = true) so TalkBack announces them as one unit (icon contentDescription = null when merged).semantics { heading() } so screen-reader users can jump between sections.semantics { traversalIndex } where needed). User-controlled contrast (Standard/Medium/High) considered if supported.MaterialTheme (no nested/conflicting themes); light + dark both tested; custom theme extensions via CompositionLocal, not globals; brand colors via a custom ColorScheme, not per-component overrides.Screen: [name] Date: [date]
| # | Category | Score | Notes |
|---|---------------------|---------|------------------------|
| 1 | Color tokens | Pass | |
| 2 | Typography | Partial | bodySmall hardcoded |
| 5 | Components | Fail | M2 Scaffold still used |
| 6 | Layout & spacing | Partial | No tablet breakpoint |
| 9 | Accessibility | Partial | Missing headings |
…
Action items:
- [ ] ...
npx claudepluginhub rcosteira79/android-skills --plugin android-skillsGuides Android UI development with Material Design 3 including Jetpack Compose, XML layouts, dynamic color, semantic color roles, typography, and accessibility patterns.
Guides building native Android apps with Material Design 3 and Jetpack Compose, covering layouts, navigation, theming, and adaptive design.
Android native development guide covering Kotlin/Compose, Material Design 3, project setup, accessibility, and build troubleshooting. Use when starting a new Android project or debugging build issues.