Agent skill

Android Design Guidelines

by ehmo in ehmo/platform-design-skills

Material Design 3 and Android platform guidelines. An agent skill from ehmo/platform-design-skills.

MITAuto-check passedMobile

Install Android Design Guidelines

skills CLI
$ npx skills add ehmo/platform-design-skills --skill android-design-guidelines -a claude-code

Project install by default; add -g for ~/.claude/skills/.

GitHub CLI
$ gh skill install ehmo/platform-design-skills android-design-guidelines --agent claude-code

Project scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).

Manual copy
$ git clone --depth 1 https://github.com/ehmo/platform-design-skills.git skills-src && mkdir -p .claude/skills && cp -r skills-src/skills/android .claude/skills/android-design-guidelines && rm -rf skills-src

Use ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.

Claude Code skills documentation · loads skills from .claude/skills/

Facts

Skill name
android-design-guidelines
GitHub stars
606
Token cost
~9.7k tokens
SKILL.md length
2,813 words
Files
4
Skills in repo
8
Repo updated
First seen
Licence
MIT

At a glance

Material Design 3 and Android platform guidelines. An agent skill from ehmo/platform-design-skills.

  • Works in 10 steps: Material You & Theming [CRITICAL] → Navigation [CRITICAL] → Layout & Responsive [HIGH] → …
  • Building Android apps with Jetpack Compose
  • SKILL.md covers 1. Material You & Theming…, 2. Navigation [CRITICAL], 3. Layout & Responsive [HIGH] and 4. Typography [HIGH], plus 2 more sections
  • Reaches schemas.android.com

What it does

Android Design Guidelines is an agent skill from ehmo/platform-design-skills. Material Design 3 and Android platform guidelines. Use when building Android apps with Jetpack Compose or XML layouts, implementing Material You, navigation, or accessibility. Triggers on tasks involving Android UI, Compose components, dynamic color, or Material Design compliance.

Its SKILL.md is about 9.7k tokens, which your agent loads only when the skill is triggered. The skill folder holds 4 other files (for example `AGENTS.md`, `metadata.json` and `rules/_sections.md`).

It sits in Mobile, covering Android development. It works with Android and Jetpack Compose. The repository describes itself as: Platform design skill pack: 300+ rules for Apple HIG, Material Design 3, and WCAG 2.2 across iOS, iPadOS, macOS, watchOS, visionOS, tvOS, Android, and Web. The licence is MIT.

When your agent uses it

  • Building Android apps with Jetpack Compose
  • Implementing Material You
  • Tasks involving Android UI
  • Compose components

Example prompts

  • “/android-design-guidelines”

Workflow steps

10 steps, taken from the step headings in SKILL.md.

  1. Material You & Theming [CRITICAL]
  2. Navigation [CRITICAL]
  3. Layout & Responsive [HIGH]
  4. Typography [HIGH]
  5. Components [HIGH]
  6. Accessibility [CRITICAL]
  7. Gestures & Input [MEDIUM]
  8. Notifications [MEDIUM]
  9. Permissions & Privacy [HIGH]
  10. System Integration [MEDIUM]

What it can do on your machine

Read from SKILL.md and the folder at commit dc2be82. It shows what the files ask for, not the result of running them.

  • Tool permissions

    Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.

    From allowed-tools in the SKILL.md frontmatter.

  • Runs code

    No scripts in the folder and no shell commands in SKILL.md (its code samples are kotlin and xml).

    From the folder's file list and the shell code blocks in SKILL.md.

  • Network

    Hosts in commands or code, which the agent is likely to contact:

    • schemas.android.com

    From URLs in SKILL.md, links to its own repository left out.

  • Credentials

    Names no API keys, tokens, secrets or passwords.

    From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.

Context cost

Android Design Guidelines loads about 9.7k tokens when it runs. Until then it costs about 77 tokens; SKILL.md has 2,813 words of instructions outside code blocks.

Always · name and description, kept in context so the agent knows when to use it
~77
When it runs · the whole SKILL.md, loaded when a task matches
~9.7k

Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.

Safety

Auto-check passed

The automated check found no risky patterns in SKILL.md.

Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.

SKILL.md

The full file from ehmo/platform-design-skills at commit dc2be82, republished under its MIT licence (© ehmo). 2,813 words, ~9,731 tokens.

Download SKILL.mdSave it as .claude/skills/android-design-guidelines/SKILL.md (or your agent's skills folder). This skill also uses 3 other files; get the full folder from GitHub.
name
android-design-guidelines
description
Material Design 3 and Android platform guidelines. Use when building Android apps with Jetpack Compose or XML layouts, implementing Material You, navigation, or accessibility. Triggers on tasks involving Android UI, Compose components, dynamic color, or Material Design compliance.
license
MIT
metadata.author
platform-design-skills
metadata.version
1.0.0

Android Platform Design Guidelines — Material Design 3

1. Material You & Theming [CRITICAL]

1.1 Dynamic Color

Enable dynamic color derived from the user's wallpaper. Dynamic color is the default on Android 12+ and should be the primary theming strategy.

kotlin
// Compose: Dynamic color theme
@Composable
fun AppTheme(
    darkTheme: Boolean = isSystemInDarkTheme(),
    dynamicColor: Boolean = true,
    content: @Composable () -> Unit
) {
    val colorScheme = when {
        dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
            val context = LocalContext.current
            if (darkTheme) dynamicDarkColorScheme(context)
            else dynamicLightColorScheme(context)
        }
        darkTheme -> darkColorScheme()
        else -> lightColorScheme()
    }
    MaterialTheme(
        colorScheme = colorScheme,
        typography = AppTypography,
        content = content
    )
}
xml
<!-- XML: Dynamic color in themes.xml -->
<style name="Theme.App" parent="Theme.Material3.DayNight.NoActionBar">
    <item name="dynamicColorThemeOverlay">@style/ThemeOverlay.Material3.DynamicColors.DayNight</item>
</style>

Rules:

  • R1.1: Always provide a fallback static color scheme for devices below Android 12.
  • R1.2: Never hardcode color hex values in components. Always reference color roles from the theme.
  • R1.3: Test with at least 3 different wallpapers to verify dynamic color harmony.
1.2 Color Roles

Material 3 defines a structured set of color roles. Use them semantically, not aesthetically.

RoleUsageOn-Role
primaryKey actions, active states, FABonPrimary
primaryContainerLess prominent primary elementsonPrimaryContainer
secondarySupporting UI, filter chipsonSecondary
secondaryContainerNavigation bar active indicatoronSecondaryContainer
tertiaryAccent, contrast, complementaryonTertiary
tertiaryContainerInput fields, less prominent accentsonTertiaryContainer
surfaceBackgrounds, cards, sheetsonSurface
surfaceVariantDecorative elements, dividersonSurfaceVariant
errorError states, destructive actionsonError
errorContainerError backgroundsonErrorContainer
outlineBorders, dividers—
outlineVariantSubtle borders—
inverseSurfaceSnackbar backgroundinverseOnSurface
kotlin
// Correct: semantic color roles
Text(
    text = "Error message",
    color = MaterialTheme.colorScheme.error
)
Surface(color = MaterialTheme.colorScheme.errorContainer) {
    Text(text = "Error detail", color = MaterialTheme.colorScheme.onErrorContainer)
}

// WRONG: hardcoded colors
Text(text = "Error", color = Color(0xFFB00020)) // Anti-pattern

Rules:

  • R1.4: Every foreground element must use the matching on color role for its background (e.g., onPrimary text on primary background).
  • R1.5: Use surface and its variants for backgrounds. Never use primary or secondary as large background areas.
  • R1.6: Use tertiary sparingly for accent and complementary contrast only.
1.3 Light and Dark Themes

Support both light and dark themes. Respect the system setting by default.

kotlin
// Compose: Detect system theme
val darkTheme = isSystemInDarkTheme()

Rules:

  • R1.7: Always support both light and dark themes. Never ship light-only.
  • R1.8: Dark theme surfaces use elevation-based tonal mapping, not pure black (#000000). Use surface color roles which handle this automatically.
  • R1.9: Provide a manual theme override in app settings (System / Light / Dark).
1.4 Custom Color Seeds

When branding requires custom colors, provide a seed color and generate tonal palettes using Material Theme Builder.

kotlin
// Custom color scheme with brand seed
private val BrandLightColorScheme = lightColorScheme(
    primary = Color(0xFF1B6D2F),
    onPrimary = Color(0xFFFFFFFF),
    primaryContainer = Color(0xFFA4F6A8),
    onPrimaryContainer = Color(0xFF002107),
    // ... generate full palette from seed
)

Rules:

  • R1.10: Generate tonal palettes from seed colors using Material Theme Builder. Never manually pick individual tones.
  • R1.11: When using custom colors, still support dynamic color as the default and use custom colors as fallback.

2. Navigation [CRITICAL]

2.1 Navigation Bar (Bottom)

The primary navigation pattern for phones with 3-5 top-level destinations.

kotlin
// Compose: Navigation Bar
NavigationBar {
    items.forEachIndexed { index, item ->
        NavigationBarItem(
            icon = {
                Icon(
                    imageVector = if (selectedItem == index) item.filledIcon else item.outlinedIcon,
                    contentDescription = item.label
                )
            },
            label = { Text(item.label) },
            selected = selectedItem == index,
            onClick = { selectedItem = index }
        )
    }
}

Rules:

  • R2.1: Use Navigation Bar for 3-5 top-level destinations on compact screens. Never use for fewer than 3 or more than 5.
  • R2.2: Always show labels on navigation bar items. Icon-only navigation bars are not permitted.
  • R2.3: Use filled icons for the selected state and outlined icons for unselected states.
  • R2.4: The active indicator uses secondaryContainer color. Do not override this.
2.2 Navigation Rail

For medium and expanded screens (tablets, foldables, desktop).

kotlin
// Compose: Navigation Rail for larger screens
NavigationRail(
    header = {
        FloatingActionButton(
            onClick = { /* primary action */ },
            containerColor = MaterialTheme.colorScheme.tertiaryContainer
        ) {
            Icon(Icons.Default.Add, contentDescription = "Create")
        }
    }
) {
    items.forEachIndexed { index, item ->
        NavigationRailItem(
            icon = { Icon(item.icon, contentDescription = item.label) },
            label = { Text(item.label) },
            selected = selectedItem == index,
            onClick = { selectedItem = index }
        )
    }
}

Rules:

  • R2.5: Use Navigation Rail on medium (600-839dp) and expanded (840dp+) window sizes. Pair it with Navigation Bar on compact.
  • R2.6: Optionally include a FAB in the rail header for the primary action.
  • R2.7: Labels are optional on the rail but recommended for clarity.
2.3 Navigation Drawer

For 5+ destinations or complex navigation hierarchies, typically on expanded screens.

kotlin
// Compose: Permanent Navigation Drawer for large screens
PermanentNavigationDrawer(
    drawerContent = {
        PermanentDrawerSheet {
            Text("App Name", modifier = Modifier.padding(16.dp),
                 style = MaterialTheme.typography.titleMedium)
            HorizontalDivider()
            items.forEach { item ->
                NavigationDrawerItem(
                    label = { Text(item.label) },
                    selected = item == selectedItem,
                    onClick = { selectedItem = item },
                    icon = { Icon(item.icon, contentDescription = null) }
                )
            }
        }
    }
) {
    Scaffold { /* page content */ }
}

Rules:

  • R2.8: Use modal drawer on compact screens, permanent drawer on expanded screens.
  • R2.9: Group drawer items into sections with dividers and section headers.
2.4 Predictive Back Gesture

Android 13+ supports predictive back with an animation preview.

kotlin
// Compose: Predictive back with BackHandler (androidx.activity.compose)
BackHandler(enabled = true) {
    // Called when back is confirmed; navigate back in your nav controller
    navController.popBackStack()
}
kotlin
// Compose: Predictive back progress animation using predictiveBackHandler modifier
// (androidx.activity:activity-compose 1.8+)
Modifier.predictiveBackHandler(enabled = true) { progress ->
    // progress is a Flow<BackEventCompat> with x, y, swipeEdge, progress (0.0–1.0)
    progress.collect { backEvent ->
        animationState = backEvent.progress
    }
}
xml
<!-- AndroidManifest.xml: opt in to predictive back -->
<application android:enableOnBackInvokedCallback="true">

Rules:

  • R2.10: Opt in to predictive back in the manifest. In Compose apps, use BackHandler (from androidx.activity.compose) to intercept back events. In View-based apps, implement OnBackInvokedCallback (API 33+) or OnBackPressedCallback (AndroidX) instead of overriding onBackPressed().
  • R2.11: The system back gesture navigates back in the navigation stack. The Up button (toolbar arrow) navigates up in the app hierarchy. These may differ.
  • R2.12: Never intercept system back to show "are you sure?" dialogs unless there is unsaved user input.
  • R2.13: Do not suppress the system-provided back preview animation. If you implement custom enter/exit transitions, interpolate them using BackEventCompat.progress (0.0–1.0) and respect BackEventCompat.swipeEdge (EDGE_LEFT/EDGE_RIGHT) so the exiting screen scales down and shifts toward the initiating edge, matching the system animation.
  • R2.14: Prefer recognition over recall. Keep destinations labeled, selected state visible, and back-stack context preserved so users do not reconstruct where they are after every navigation step.
kotlin
// Compose: drive a custom animation from predictive back progress
Modifier.predictiveBackHandler(enabled = true) { progress ->
    progress.collect { backEvent ->
        // backEvent.progress: 0.0 (gesture start) → 1.0 (committed)
        // backEvent.swipeEdge: BackEventCompat.EDGE_LEFT or EDGE_RIGHT
        exitScale = 1f - (backEvent.progress * 0.1f)
        exitOffsetX = if (backEvent.swipeEdge == BackEventCompat.EDGE_LEFT) -backEvent.progress * 32.dp.toPx() else backEvent.progress * 32.dp.toPx()
    }
}
2.5 Navigation Component Selection
Screen Size3-5 Destinations5+ Destinations
Compact (< 600dp)Navigation BarModal Drawer + Navigation Bar
Medium (600-839dp)Navigation RailModal Drawer + Navigation Rail
Expanded (840dp+)Navigation RailPermanent Drawer

3. Layout & Responsive [HIGH]

3.1 Window Size Classes

Use window size classes for adaptive layouts, not raw pixel breakpoints.

kotlin
// Compose: Window size classes
val windowSizeClass = calculateWindowSizeClass(this)
when (windowSizeClass.widthSizeClass) {
    WindowWidthSizeClass.Compact -> CompactLayout()
    WindowWidthSizeClass.Medium -> MediumLayout()
    WindowWidthSizeClass.Expanded -> ExpandedLayout()
}
ClassWidthTypical DeviceColumns
Compact< 600dpPhone portrait4
Medium600-839dpTablet portrait, foldable8
Expanded840dp+Tablet landscape, desktop12

Rules:

  • R3.1: Always use WindowSizeClass from material3-window-size-class for responsive layout decisions.
  • R3.2: Never use fixed pixel breakpoints. Device categories are fluid.
  • R3.3: Support all three width size classes. At minimum, compact and expanded.
3.2 Material Grid

Apply canonical Material grid margins and gutters.

Size ClassMarginsGuttersColumns
Compact16dp8dp4
Medium24dp16dp8
Expanded24dp24dp12

Rules:

  • R3.4: Content should not span the full width on expanded screens. Use a max content width of ~840dp or list-detail layout.
  • R3.5: Apply consistent horizontal margins matching the grid spec.
3.3 Edge-to-Edge Display

Android 15+ enforces edge-to-edge. All apps should draw behind system bars.

kotlin
// Compose: Edge-to-edge setup
class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        enableEdgeToEdge()
        super.onCreate(savedInstanceState)
        setContent {
            Scaffold(
                modifier = Modifier.fillMaxSize(),
                // Scaffold handles insets for top/bottom bars automatically
            ) { innerPadding ->
                Content(modifier = Modifier.padding(innerPadding))
            }
        }
    }
}

Rules:

  • R3.6: Call enableEdgeToEdge() before setContent. Draw behind both status bar and navigation bar.
  • R3.7: Use WindowInsets to pad content away from system bars. Scaffold handles this for top bar and bottom bar content automatically.
  • R3.8: Scrollable content should scroll behind transparent system bars with appropriate inset padding at the top and bottom of the list.
3.4 Foldable Device Support
kotlin
// Compose: Detect fold posture
val foldingFeatures = WindowInfoTracker.getOrCreate(context)
    .windowLayoutInfo(context)
    .collectAsState(initial = WindowLayoutInfo(emptyList()))

Rules:

  • R3.9: Detect hinge/fold position and avoid placing critical content across the fold.
  • R3.10: Use ListDetailPaneScaffold or SupportingPaneScaffold from Material3 adaptive library for foldable-aware layouts.

4. Typography [HIGH]

4.1 Material Type Scale
RoleDefault SizeDefault WeightUsage
displayLarge57sp400Hero text, onboarding
displayMedium45sp400Large feature text
displaySmall36sp400Prominent display
headlineLarge32sp400Screen titles
headlineMedium28sp400Section headers
headlineSmall24sp400Card titles
titleLarge22sp400Top app bar title
titleMedium16sp500Tabs, navigation
titleSmall14sp500Subtitles
bodyLarge16sp400Primary body text
bodyMedium14sp400Secondary body text
bodySmall12sp400Captions
labelLarge14sp500Buttons, prominent labels
labelMedium12sp500Chips, smaller labels
labelSmall11sp500Timestamps, annotations
kotlin
// Compose: Custom typography
val AppTypography = Typography(
    displayLarge = TextStyle(
        fontFamily = FontFamily(Font(R.font.brand_regular)),
        fontWeight = FontWeight.Normal,
        fontSize = 57.sp,
        lineHeight = 64.sp,
        letterSpacing = (-0.25).sp
    ),
    bodyLarge = TextStyle(
        fontFamily = FontFamily(Font(R.font.brand_regular)),
        fontWeight = FontWeight.Normal,
        fontSize = 16.sp,
        lineHeight = 24.sp,
        letterSpacing = 0.5.sp
    )
    // ... define all 15 roles
)

Rules:

  • R4.1: Always use sp units for text sizes to support user font scaling preferences.
  • R4.2: Never set text below 12sp for body content. Labels may go to 11sp minimum.
  • R4.3: Reference typography roles from MaterialTheme.typography, not hardcoded sizes.
  • R4.4: Support dynamic type scaling. Test at 200% font scale. Ensure no text is clipped or overlapping.
  • R4.5: Line height should be approximately 1.2-1.5x the font size for readability.

5. Components [HIGH]

5.1 Floating Action Button (FAB)

The FAB represents the single most important action on a screen.

kotlin
// Compose: FAB variants
// Standard FAB
FloatingActionButton(onClick = { /* action */ }) {
    Icon(Icons.Default.Add, contentDescription = "Create new item")
}

// Extended FAB (with label - preferred for clarity)
ExtendedFloatingActionButton(
    onClick = { /* action */ },
    icon = { Icon(Icons.Default.Edit, contentDescription = null) },
    text = { Text("Compose") }
)

// Large FAB
LargeFloatingActionButton(onClick = { /* action */ }) {
    Icon(Icons.Default.Add, contentDescription = "Create", modifier = Modifier.size(36.dp))
}

Rules:

  • R5.1: Use at most one FAB per screen. It represents the primary action.
  • R5.2: Place the FAB at the bottom-end of the screen. On screens with a Navigation Bar, the FAB floats above it.
  • R5.3: The FAB should use primaryContainer color by default. Use tertiaryContainer for secondary screens.
  • R5.4: Prefer ExtendedFloatingActionButton with a label for clarity. Collapse to icon-only on scroll if needed.
5.2 Top App Bar
kotlin
// Compose: Top app bar variants
// Small (default)
TopAppBar(
    title = { Text("Page Title") },
    navigationIcon = {
        IconButton(onClick = { /* navigate up */ }) {
            Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back")
        }
    },
    actions = {
        IconButton(onClick = { /* search */ }) {
            Icon(Icons.Default.Search, contentDescription = "Search")
        }
    }
)

// Medium — expands title area
MediumTopAppBar(
    title = { Text("Section Title") },
    scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior()
)

// Large — for prominent titles
LargeTopAppBar(
    title = { Text("Screen Title") },
    scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
)

Rules:

  • R5.5: Use TopAppBar (small) for most screens. Use MediumTopAppBar or LargeTopAppBar for prominent section or screen titles.
  • R5.6: Connect scroll behavior to the app bar so it collapses/expands with content scrolling.
  • R5.7: Limit action icons to 2-3. Overflow additional actions into a more menu.
5.3 Bottom Sheets
kotlin
// Compose: Modal bottom sheet
ModalBottomSheet(
    onDismissRequest = { showSheet = false },
    sheetState = rememberModalBottomSheetState()
) {
    Column(modifier = Modifier.padding(16.dp)) {
        Text("Sheet Title", style = MaterialTheme.typography.titleLarge)
        Spacer(modifier = Modifier.height(16.dp))
        // Sheet content
    }
}

Rules:

  • R5.8: Use modal bottom sheets for non-critical supplementary content. Use standard bottom sheets for persistent content.
  • R5.9: Bottom sheets must have a visible drag handle for discoverability.
  • R5.10: Sheet content must be scrollable if it can exceed the visible area.
5.4 Dialogs
kotlin
// Compose: Alert dialog
AlertDialog(
    onDismissRequest = { showDialog = false },
    title = { Text("Discard draft?") },
    text = { Text("Your unsaved changes will be lost.") },
    confirmButton = {
        TextButton(onClick = { /* confirm */ }) { Text("Discard") }
    },
    dismissButton = {
        TextButton(onClick = { showDialog = false }) { Text("Cancel") }
    }
)

Rules:

  • R5.11: Dialogs interrupt the user. Use them only for critical decisions requiring immediate attention.
  • R5.12: Confirm button uses a text button, not a filled button. The dismiss button is always on the left.
  • R5.13: Dialog titles should be concise questions or statements. Body text provides context.
5.5 Snackbar
kotlin
// Compose: Snackbar with action
val snackbarHostState = remember { SnackbarHostState() }
Scaffold(snackbarHost = { SnackbarHost(snackbarHostState) }) {
    // trigger snackbar
    LaunchedEffect(key) {
        val result = snackbarHostState.showSnackbar(
            message = "Item archived",
            actionLabel = "Undo",
            duration = SnackbarDuration.Short
        )
        if (result == SnackbarResult.ActionPerformed) { /* undo */ }
    }
}

Rules:

  • R5.14: Use snackbars for brief, non-critical feedback. They auto-dismiss and should not contain critical information.
  • R5.15: Snackbars appear at the bottom of the screen, above the Navigation Bar and below the FAB.
  • R5.16: Include an action (e.g., "Undo") when the operation is reversible. Limit to one action.
5.6 Chips
kotlin
// Filter Chip
FilterChip(
    selected = isSelected,
    onClick = { isSelected = !isSelected },
    label = { Text("Filter") },
    leadingIcon = if (isSelected) {
        { Icon(Icons.Default.Check, contentDescription = null, modifier = Modifier.size(18.dp)) }
    } else null
)

// Assist Chip
AssistChip(
    onClick = { /* action */ },
    label = { Text("Add to calendar") },
    leadingIcon = { Icon(Icons.Default.CalendarToday, contentDescription = null) }
)

Rules:

  • R5.17: Use FilterChip for toggling filters, AssistChip for smart suggestions, InputChip for user-entered content (tags), SuggestionChip for dynamically generated suggestions.
  • R5.18: Chips should be arranged in a horizontally scrollable row or a flow layout, not stacked vertically.
  • R5.19: Expose waiting states immediately. If an action cannot finish right away, acknowledge it with inline state change, progress, or another visible response rather than leaving the UI static.
5.7 Component Selection Guide
NeedComponent
Primary screen actionFAB
Brief feedbackSnackbar
Critical decisionDialog
Supplementary contentBottom Sheet
Toggle filterFilter Chip
User-entered tagInput Chip
Smart suggestionAssist Chip
Content groupCard
Vertical list of itemsLazyColumn with ListItem
Segmented option (2-5)SegmentedButton
Binary toggleSwitch
Selection from listRadio buttons or exposed dropdown menu

6. Accessibility [CRITICAL]

6.1 TalkBack and Content Descriptions
kotlin
// Compose: Accessible components
Icon(
    Icons.Default.Favorite,
    contentDescription = "Add to favorites" // Descriptive, not "heart icon"
)

// Decorative elements
Icon(
    Icons.Default.Star,
    contentDescription = null // null for purely decorative
)

// Merge semantics for compound elements
Row(modifier = Modifier.semantics(mergeDescendants = true) {}) {
    Icon(Icons.Default.Event, contentDescription = null)
    Text("March 15, 2026")
}

// Custom actions
Box(modifier = Modifier.semantics {
    customActions = listOf(
        CustomAccessibilityAction("Archive") { /* archive */ true },
        CustomAccessibilityAction("Delete") { /* delete */ true }
    )
})

Rules:

  • R6.1: Every interactive element must have a contentDescription (or null if purely decorative).
  • R6.2: Content descriptions must describe the action or meaning, not the visual appearance. Say "Add to favorites" not "Heart icon."
  • R6.3: Use mergeDescendants = true to group related elements into a single TalkBack focus unit (e.g., a list item with icon + text + subtitle).
  • R6.4: Provide customActions for swipe-to-dismiss or long-press actions so TalkBack users can access them.
6.2 Touch Targets
kotlin
// Compose: Ensure minimum touch target
IconButton(onClick = { /* action */ }) {
    // IconButton already provides 48dp minimum touch target
    Icon(Icons.Default.Close, contentDescription = "Close")
}

// Manual minimum touch target
Box(
    modifier = Modifier
        .sizeIn(minWidth = 48.dp, minHeight = 48.dp)
        .clickable { /* action */ },
    contentAlignment = Alignment.Center
) {
    Icon(Icons.Default.Info, contentDescription = "Info", modifier = Modifier.size(24.dp))
}

Rules:

  • R6.5: All interactive elements must have a minimum touch target of 48x48dp. Material 3 components handle this by default.
  • R6.6: Do not reduce touch targets to save space. Use padding to increase the touchable area if the visual element is smaller.
6.3 Color Contrast and Visual

Rules:

  • R6.7: Text contrast ratio must be at least 4.5:1 for normal text and 3:1 for large text (18sp+ or 14sp+ bold) against its background.
  • R6.8: Never use color as the only means of conveying information. Pair with icons, text, or patterns.
  • R6.9: Support bold text and high contrast accessibility settings. Use Configuration.fontWeightAdjustment (API 31+) to detect the user's bold text preference and scale custom font weights accordingly. Use AccessibilityManager.isHighTextContrastEnabled() to detect high contrast mode and substitute higher-contrast color values. Material 3 components handle both automatically; custom text rendering and color usage must opt in explicitly.
kotlin
// Detect bold text preference (API 31+)
val fontWeightAdjustment = resources.configuration.fontWeightAdjustment
val isBoldText = fontWeightAdjustment >= 700 // equivalent to FontWeight.Bold.weight

// Detect high contrast mode
val am = getSystemService(Context.ACCESSIBILITY_SERVICE) as AccessibilityManager
val isHighContrast = am.isHighTextContrastEnabled

// Compose: use MaterialTheme.typography which respects fontWeightAdjustment automatically
Text(
    text = "Label",
    style = MaterialTheme.typography.bodyLarge // Adapts to fontWeightAdjustment
)

// For custom colors: provide high-contrast alternative
val labelColor = if (isHighContrast) {
    MaterialTheme.colorScheme.onSurface  // Strong contrast
} else {
    MaterialTheme.colorScheme.onSurfaceVariant  // Normal contrast
}
Show full SKILL.md (1,102 more words)Show less
6.4 Focus and Traversal
kotlin
// Compose: Custom focus order
Column {
    var focusRequester = remember { FocusRequester() }
    TextField(
        modifier = Modifier.focusRequester(focusRequester),
        value = text,
        onValueChange = { text = it }
    )
    LaunchedEffect(Unit) {
        focusRequester.requestFocus() // Auto-focus on screen load
    }
}

Rules:

  • R6.10: Focus order must follow a logical reading sequence (top-to-bottom, start-to-end). Avoid custom focusOrder unless the default is incorrect.
  • R6.11: After navigation or dialog dismissal, move focus to the most logical target element.
  • R6.12: All screens must be fully operable using TalkBack, Switch Access, and external keyboard.
6.5 Custom Canvas Views

Custom View subclasses that draw content on a Canvas (charts, custom pickers, drawing surfaces) are invisible to TalkBack by default because they have no child views. Use ExploreByTouchHelper from androidx.customview.widget to define a virtual accessibility tree.

  • R6.13: Custom canvas-drawn views must use ExploreByTouchHelper to expose a virtual accessibility tree to TalkBack. Override getVirtualViewAt() to map touch coordinates to virtual view IDs, and onPopulateNodeForVirtualView() to supply text, bounds, and actions for each virtual node.
kotlin
import androidx.customview.widget.ExploreByTouchHelper

class PieChartView(context: Context) : View(context) {

    private val helper = object : ExploreByTouchHelper(this) {
        override fun getVirtualViewAt(x: Float, y: Float): Int {
            // Return virtual view ID for the slice at (x, y), or INVALID_ID
            return sliceIndexAt(x, y)
        }

        override fun getVisibleVirtualViews(virtualViewIds: MutableList<Int>) {
            slices.indices.forEach { virtualViewIds.add(it) }
        }

        override fun onPopulateNodeForVirtualView(
            virtualViewId: Int,
            node: AccessibilityNodeInfoCompat
        ) {
            val slice = slices[virtualViewId]
            node.text = "${slice.label}: ${slice.percentage}%"
            node.setBoundsInParent(slice.bounds)
            node.addAction(AccessibilityNodeInfoCompat.ACTION_CLICK)
        }

        override fun onPerformActionForVirtualView(
            virtualViewId: Int, action: Int, arguments: Bundle?
        ): Boolean {
            if (action == AccessibilityNodeInfoCompat.ACTION_CLICK) {
                onSliceSelected(virtualViewId)
                return true
            }
            return false
        }
    }

    init {
        ViewCompat.setAccessibilityDelegate(this, helper)
    }

    override fun dispatchHoverEvent(event: MotionEvent) =
        helper.dispatchHoverEvent(event) || super.dispatchHoverEvent(event)
}

7. Gestures & Input [MEDIUM]

7.1 System Gestures

Rules:

  • R7.1: Never place interactive elements within the system gesture inset zones (bottom 20dp, left/right 24dp edges) as they conflict with system navigation gestures.
  • R7.2: Use WindowInsets.systemGestures to detect and avoid gesture conflict zones.
7.2 Common Gesture Patterns
kotlin
// Compose: Pull to refresh
PullToRefreshBox(
    isRefreshing = isRefreshing,
    onRefresh = { viewModel.refresh() }
) {
    LazyColumn { /* content */ }
}

// Compose: Swipe to dismiss
SwipeToDismissBox(
    state = rememberSwipeToDismissBoxState(),
    backgroundContent = {
        Box(
            modifier = Modifier.fillMaxSize().background(MaterialTheme.colorScheme.error),
            contentAlignment = Alignment.CenterEnd
        ) {
            Icon(Icons.Default.Delete, contentDescription = "Delete",
                 tint = MaterialTheme.colorScheme.onError)
        }
    }
) {
    ListItem(headlineContent = { Text("Swipeable item") })
}

Rules:

  • R7.3: All swipe-to-dismiss actions must be undoable (show snackbar with undo) or require confirmation.
  • R7.4: Provide alternative non-gesture ways to trigger all gesture-based actions (for accessibility).
  • R7.5: Apply Material ripple effect on all tappable elements. Compose clickable modifier includes ripple by default.
7.3 Long Press

Rules:

  • R7.6: Use long press for contextual menus and multi-select mode. Never use it as the only way to access a feature.
  • R7.7: Provide haptic feedback on long press via HapticFeedbackType.LongPress.

8. Notifications [MEDIUM]

8.1 Notification Channels
kotlin
// Create notification channel (required for Android 8+)
val channel = NotificationChannel(
    "messages",
    "Messages",
    NotificationManager.IMPORTANCE_HIGH
).apply {
    description = "New message notifications"
    enableLights(true)
    lightColor = Color.BLUE
}
notificationManager.createNotificationChannel(channel)
ImportanceBehaviorUse For
IMPORTANCE_HIGHSound + heads-upMessages, calls
IMPORTANCE_DEFAULTSoundSocial updates, emails
IMPORTANCE_LOWNo soundRecommendations
IMPORTANCE_MINSilent, no status barWeather, ongoing

Rules:

  • R8.1: Create separate notification channels for each distinct notification type. Users can configure each independently.
  • R8.2: Choose importance levels conservatively. Overusing IMPORTANCE_HIGH leads users to disable notifications entirely.
  • R8.3: All notifications must have a tap action (PendingIntent) that navigates to relevant content.
  • R8.4: Include a contentDescription in notification icons for accessibility.
8.2 Notification Design

Rules:

  • R8.5: Use MessagingStyle for conversations. Include sender name and avatar.
  • R8.6: Add direct reply actions to messaging notifications.
  • R8.7: Provide a "Mark as read" action on message notifications.
  • R8.8: Use expandable notifications (BigTextStyle, BigPictureStyle, InboxStyle) for rich content.
  • R8.9: Foreground service notifications must accurately describe the ongoing operation and provide a stop action where appropriate.

9. Permissions & Privacy [HIGH]

9.1 Runtime Permissions
kotlin
// Compose: Permission request
val permissionState = rememberPermissionState(Manifest.permission.CAMERA)

if (permissionState.status.isGranted) {
    CameraPreview()
} else {
    Column {
        Text("Camera access is needed to scan QR codes.")
        Button(onClick = { permissionState.launchPermissionRequest() }) {
            Text("Grant Camera Access")
        }
    }
}

Rules:

  • R9.1: Request permissions in context, at the moment they are needed, not at app launch.
  • R9.2: Always explain why the permission is needed before requesting it (rationale screen).
  • R9.3: Gracefully handle permission denial. Provide degraded functionality rather than blocking the user.
  • R9.4: Never request permissions you do not actively use. Google Play will reject apps with unnecessary permissions.
9.2 Privacy-Preserving APIs
kotlin
// Photo picker: no permission needed
val pickMedia = rememberLauncherForActivityResult(
    ActivityResultContracts.PickVisualMedia()
) { uri ->
    uri?.let { /* handle selected media */ }
}
pickMedia.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly))

Rules:

  • R9.5: Use the Photo Picker (Android 13+) instead of requesting READ_MEDIA_IMAGES. No permission needed.
  • R9.6: Use ACCESS_COARSE_LOCATION (approximate) unless precise location is essential for functionality.
  • R9.7: Prefer one-time permissions for camera and microphone in non-recording contexts.
  • R9.8: Display a privacy indicator when camera or microphone is actively in use.

10. System Integration [MEDIUM]

10.1 Widgets
kotlin
// Compose Glance API widget
class TaskWidget : GlanceAppWidget() {
    override suspend fun provideGlance(context: Context, id: GlanceId) {
        provideContent {
            GlanceTheme {
                Column(
                    modifier = GlanceModifier
                        .fillMaxSize()
                        .background(GlanceTheme.colors.widgetBackground)
                        .padding(16.dp)
                ) {
                    Text(
                        text = "Tasks",
                        style = TextStyle(fontWeight = FontWeight.Bold,
                                         color = GlanceTheme.colors.onSurface)
                    )
                    // Widget content
                }
            }
        }
    }
}

Rules:

  • R10.1: Use Glance API for new widgets. Support dynamic color via GlanceTheme.
  • R10.2: Widgets must have a default configuration and work immediately after placement.
  • R10.3: Provide multiple widget sizes (small, medium, large) where practical.
  • R10.4: Use rounded corners matching the system widget shape (system_app_widget_background_radius).
10.2 App Shortcuts
xml
<!-- shortcuts.xml -->
<shortcuts xmlns:android="http://schemas.android.com/apk/res/android">
    <shortcut
        android:shortcutId="compose"
        android:enabled="true"
        android:shortcutShortLabel="@string/compose_short"
        android:shortcutLongLabel="@string/compose_long"
        android:icon="@drawable/ic_shortcut_compose">
        <intent
            android:action="android.intent.action.VIEW"
            android:targetPackage="com.example.app"
            android:targetClass="com.example.app.ComposeActivity" />
    </shortcut>
</shortcuts>

Rules:

  • R10.5: Provide 2-4 static shortcuts for common actions. Support dynamic shortcuts for recent content.
  • R10.6: Shortcut icons should be simple, recognizable silhouettes on a circular background.
  • R10.7: Test shortcuts with long-press on the app icon and in the Settings > Apps shortcut list.

Rules:

  • R10.8: Support Android App Links (verified deep links) for all public content URLs.
  • R10.9: Implement the share sheet with ShareCompat or Intent.createChooser. Provide rich previews with title, description, and thumbnail.
  • R10.10: Handle incoming share intents with appropriate content type filtering.

Design Evaluation Checklist

Use this checklist to evaluate Android UI implementations:

Theme & Color
  • Dynamic color enabled with static fallback
  • All colors reference Material theme roles (no hardcoded hex)
  • Light and dark themes both supported
  • On-colors match their background color roles
  • Custom colors generated from seed via Material Theme Builder
Navigation
  • Correct navigation component for screen size and destination count
  • Navigation bar labels always visible
  • Predictive back gesture opted in and handled
  • Up vs Back behavior correct
Layout
  • All three window size classes supported
  • Edge-to-edge with proper inset handling
  • Content does not span full width on large screens
  • Foldable hinge area respected
Typography
  • All text uses sp units
  • All text references MaterialTheme.typography roles
  • Tested at 200% font scale with no clipping
  • Minimum 12sp body, 11sp labels
Components
  • At most one FAB per screen
  • Top app bar connected to scroll behavior
  • Snackbars used for non-critical feedback only
  • Dialogs reserved for critical interruptions
Accessibility
  • All interactive elements have contentDescription
  • All touch targets >= 48dp
  • Color contrast >= 4.5:1 for text
  • No information conveyed by color alone
  • Full TalkBack traversal tested
  • Switch Access and keyboard navigation work
Gestures
  • No interactive elements in system gesture zones
  • All gesture actions have non-gesture alternatives
  • Swipe-to-dismiss is undoable
Notifications
  • Separate channels for each notification type
  • Appropriate importance levels
  • Tap action navigates to relevant content
Permissions
  • Permissions requested in context, not at launch
  • Rationale shown before permission request
  • Graceful degradation on denial
  • Photo Picker used instead of media permission
System Integration
  • Widgets use Glance API with dynamic color
  • App shortcuts provided for common actions
  • Deep links handled for public content

Anti-Patterns

Anti-PatternWhy It Is WrongCorrect Approach
Hardcoded color hex valuesBreaks dynamic color and dark themeUse MaterialTheme.colorScheme roles
Using dp for text sizeIgnores user font scalingUse sp units
Custom bottom navigation barInconsistent with platformUse Material NavigationBar
Navigation bar without labelsViolates Material guidelinesAlways show labels
Dialog for non-critical infoInterrupts user unnecessarilyUse Snackbar or Bottom Sheet
FAB for secondary actionsDilutes primary action prominenceOne FAB for the primary action only
onBackPressed() overrideDeprecated; breaks predictive backUse BackHandler (Compose) or OnBackInvokedCallback (View-based) for predictive back support
Touch targets < 48dpAccessibility violationEnsure minimum 48x48dp
Permission request at launchUsers deny without contextRequest in context with rationale
Pure black (#000000) dark themeEye strain; not Material 3Use Material surface color roles
Icon-only navigation barUsers cannot identify destinationsAlways include text labels
Full-width content on tabletsWastes space; poor readabilityMax width or list-detail layout
READ_EXTERNAL_STORAGE for photosUnnecessary since Android 13Use Photo Picker API
Blocking UI on permission denialPunishes the userGraceful degradation
Manual color palette selectionInconsistent tonal relationshipsUse Material Theme Builder

© ehmo, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file

Files

SKILL.md and 3 other files in skills/android of ehmo/platform-design-skills.

  • SKILL.md
  • AGENTS.md
  • metadata.json
  • rules/_sections.md

Open the folder on GitHubat commit dc2be82

Compare with similar skills

Android Design Guidelines next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.

Android Design Guidelines compared with similar skills
SkillStarsUsed inTokensAuto-checkLicenceRepo updated
Android Design Guidelines this skillehmo/platform-design-skills606—~9.7kAutomated safety check: PassMIT
Compose Multiplatform Patternsmonta-app/ocpp-emulator1805 repos~2kAutomated safety check: PassApache-2.0
Stylesarindamxd/camerax-android1324 repos~2.3kAutomated safety check: PassApache-2.0
Android Compose Patternsmakifbaysal/tasktrooper109—~1.9kAutomated safety check: PassApache-2.0
Configuring Test Dependenciesskydoves/android-testing-skills333—~4.1kAutomated safety check: PassApache-2.0
Setting Up Host Vs Device Testsskydoves/android-testing-skills333—~4.8kAutomated safety check: PassApache-2.0

Similar skills

  • Compose Multiplatform Patterns

    monta-app/ocpp-emulator

    Compose Multiplatform and Jetpack Compose patterns for KMP projects — state management, navigation, theming, performance, and platform-specific UI.

    180 GitHub starsUsed in 5 repos~2k tokens
    MobileAuto-check passed
  • Styles

    arindamxd/camerax-android

    A skill your agent uses to integrate the Jetpack Compose Styles API into an Android project.

    132 GitHub starsUsed in 4 repos~2.3k tokens
    MobileAuto-check passed
  • Android Compose Patterns

    makifbaysal/tasktrooper

    A skill your agent uses when building native Android with Jetpack Compose - stateless composables, state hoisting, ViewModel-owned state, edge-to-edge, predictive back, adaptive layout, atomic…

    109 GitHub stars~1.9k tokensUpdated today
    MobileAuto-check passed
  • Configuring Test Dependencies

    skydoves/android-testing-skills

    A skill your agent uses to wire the correct Gradle dependency matrix for Jetpack Compose UI tests.

    333 GitHub stars~4.1k tokensUpdated 4 mo ago
    MobileAuto-check passed
  • Setting Up Host Vs Device Tests

    skydoves/android-testing-skills

    A skill your agent uses to choose between host (Robolectric/JVM) and device (instrumentation) tests for Jetpack Compose, and to configure each correctly.

    333 GitHub stars~4.8k tokensUpdated 4 mo ago
    MobileAuto-check passed
  • Jetpack Compose Expert

    aldefy/compose-skill

    Guides Jetpack Compose and Compose Multiplatform work across Android, Desktop, iOS and web, plus Android TV, Material 3 motion, paging, navigation and design-to-code.

    596 GitHub stars~4.8k tokensUpdated 2 mo ago
    MobileAuto-check passed

More from ehmo/platform-design-skills

All 8 skills in this repo
  • Tvos Design Guidelines

    ehmo/platform-design-skills

    Apple Human Interface Guidelines for Apple TV. An agent skill from ehmo/platform-design-skills.

    606 GitHub stars~4.8k tokensUpdated 6 mo ago
    Auto-check passed
  • macOS Design Guidelines

    ehmo/platform-design-skills

    Apple Human Interface Guidelines for Mac. An agent skill from ehmo/platform-design-skills.

    606 GitHub starsUsed in 1 repo~9.4k tokens
    Auto-check passed
  • Watchos Design Guidelines

    ehmo/platform-design-skills

    Apple Human Interface Guidelines for Apple Watch. An agent skill from ehmo/platform-design-skills.

    606 GitHub stars~4.3k tokensUpdated 6 mo ago
    Auto-check passed
  • iOS Design Guidelines

    ehmo/platform-design-skills

    Apple Human Interface Guidelines for iPhone. An agent skill from ehmo/platform-design-skills.

    606 GitHub stars~9k tokensUpdated 6 mo ago
    Auto-check passed
  • Ipados Design Guidelines

    ehmo/platform-design-skills

    Apple Human Interface Guidelines for iPad. An agent skill from ehmo/platform-design-skills.

    606 GitHub stars~6.4k tokensUpdated 6 mo ago
    Auto-check passed
  • Visionos Design Guidelines

    ehmo/platform-design-skills

    Apple Human Interface Guidelines for Apple Vision Pro. An agent skill from ehmo/platform-design-skills.

    606 GitHub stars~5.7k tokensUpdated 6 mo ago
    Auto-check passed

Questions about Android Design Guidelines

What does Android Design Guidelines do?

Material Design 3 and Android platform guidelines. An agent skill from ehmo/platform-design-skills. Android Design Guidelines is an agent skill from ehmo/platform-design-skills. Material Design 3 and Android platform guidelines.

When should I use Android Design Guidelines?

Android Design Guidelines fits situations like: building Android apps with Jetpack Compose; implementing Material You; tasks involving Android UI; compose components.

How do I install Android Design Guidelines in Claude Code?

Run `npx skills add ehmo/platform-design-skills --skill android-design-guidelines -a claude-code`. Or copy the skill folder (skills/android in ehmo/platform-design-skills) into .claude/skills/android-design-guidelines in your project. Claude Code loads it when a task matches its description.

How do I install Android Design Guidelines in Codex?

Run `npx skills add ehmo/platform-design-skills --skill android-design-guidelines -a codex`. Or copy the skill folder (skills/android in ehmo/platform-design-skills) into .agents/skills/android-design-guidelines in your project. Codex loads it when a task matches its description.

Can I use Android Design Guidelines in Cursor, Gemini CLI or GitHub Copilot?

Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add ehmo/platform-design-skills --skill android-design-guidelines -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/android-design-guidelines, .gemini/skills/android-design-guidelines, .github/skills/android-design-guidelines and .opencode/skills/android-design-guidelines in your project.

What does Android Design Guidelines need to run?

SKILL.md names no scripts, command-line tools or credentials: Android Design Guidelines is instructions for the agent only.

Does Android Design Guidelines access the network?

SKILL.md names 1 domain. In commands or code: schemas.android.com; the agent is likely to contact it when it follows the instructions. This is read from the text; nothing was executed.

Is Android Design Guidelines safe to install?

Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.

What licence does Android Design Guidelines use?

Android Design Guidelines is published under the MIT licence (declared in SKILL.md). It allows redistribution, so the full SKILL.md is shown on this page.

How many tokens does Android Design Guidelines use?

About 9.7k tokens (SKILL.md is roughly 39k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.

What are the alternatives to Android Design Guidelines?

Skills that share tags, products or a category with Android Design Guidelines: Compose Multiplatform Patterns (monta-app/ocpp-emulator, 180 stars), Styles (arindamxd/camerax-android, 132 stars), Android Compose Patterns (makifbaysal/tasktrooper, 109 stars) and Configuring Test Dependencies (skydoves/android-testing-skills, 333 stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.

Who maintains Android Design Guidelines?

ehmo (a GitHub user) maintains it in ehmo/platform-design-skills, which has 606 GitHub stars. The repository holds 8 skills in this directory. The repository was last updated on March 19, 2026.

Source: ehmo/platform-design-skills on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.