Theming¶
AppTheme extends Material 3 with semantic tokens. Components read colors, spacing, typography, shapes, motion, and icon contracts from AppTheme; apps supply their own values into the same slots.
Token bundles¶
| Slot bundle | Type | Default | Accessor |
|---|---|---|---|
| Colors | AppColors |
AppColors.light() / AppColors.dark() |
AppTheme.colors |
| Spacing | AppSpacing |
AppSpacing.default() (8-dp grid) |
AppTheme.spacing |
| Typography | AppTypography |
AppTypography.default() (M3 scale + extensions) |
AppTheme.typography |
| Shapes | AppShapes |
AppShapes.default() (standard/small/large/image/pill/badge) |
AppTheme.shapes |
| Motion | AppMotion |
AppMotion.default() (short/medium/long + emphasized easing) |
AppTheme.motion |
| Icons | AppIcons |
AppIcons.default() (placeholder glyphs) |
AppTheme.icons |
For non-@Composable code that needs default spacing values, the static AppSpacingDefaults object exposes the same defaults as plain Dp constants.
Light + dark color pair¶
AppTheme takes both light and dark color schemes and swaps between them based on darkTheme. Apps can customize both:
AppTheme(
lightColors = AppColors.light().copy(primary = Color(0xFF005AC1)),
darkColors = AppColors.dark().copy(primary = Color(0xFFA8C7FA)),
) {
Text(text = "Content")
}
If only one set is provided, the other defaults to the library's neutral defaults.
Dynamic color¶
Android 12+ dynamic color is opt-in:
AppTheme(dynamicColor = true) {
Text(text = "Content")
}
When dynamic color is unavailable, AppTheme falls back to the supplied AppColors and the current dark mode setting. Extension slots (status, overlay, level palette) are always sourced from the supplied AppColors, never overridden by the dynamic Material You scheme.
Color slot taxonomy¶
The AppColors data class groups slots into five buckets:
- Core (Material 3 standard) —
primary,onPrimary,primaryContainer,onPrimaryContainer,secondary,onSecondary,background,onBackground,surface,onSurface,surfaceVariant,onSurfaceVariant,surfaceContainer,outline,outlineVariant,error,onError,scrim. These map straight onto Material 3'sColorScheme. - Status (extensions) —
success,onSuccess,warning,onWarning. Apps that need success/warning indicators no longer have to repurposetertiaryor invent their own. - Overlay (extensions) —
overlayHeavy,overlayMedium,overlaySubtle,overlayShadowStart,overlayShadowEnd,overlayBottomTint. Useful for media-overlay UIs (player thumbnails, gradient scrims) without baking magic alphas into call sites. - Levels (extension) —
levels: LevelPalette. ALevelPaletteis an ordered list ofLevelTier(background, foreground)pairs that powers tiered-badge usages likePillChip(label, tier). Apps own the ordering and color values. - Skeleton (extension) —
shimmerHighlight. The highlightModifier.skeletonShimmer()sweeps across loading placeholders. Defaults toonSurface(the placeholders sit on neutral surface fills, so the sweep needs the theme's ink color); override it for skeletons on an unusual surface.
Spacing slot taxonomy¶
Two parallel naming schemes are exposed for back-compat:
- Multiplier scale —
half(4 dp),x1(8 dp),x1_5(12 dp),x2(16 dp),x3(24 dp),x4(32 dp),x5(40 dp),x6(48 dp),x9(72 dp). Preferred for new code. - T-shirt scale —
xs(4 dp) …xl(24 dp). Kept for back-compat and Material-style component sizing. - Semantic aliases —
padding,contentPadding,itemSpacing,gridSpacing. - Strokes —
strokeThin,strokeRegular,strokeThick. - Interaction —
minTapTargetdefaults to 44 dp for compatibility. Interactive components enforce at least 48 dp through the shared minimum-target helpers. - Layout dimensions (apps override) —
gridItemMinWidth,gridImageHeight,videoCardThumbnailHeight,maxContentWidthForm,maxContentWidthList,maxContentWidthPlayer.AppSpacing.default()setsmaxContentWidthFormto 600 dp. The other layout dimensions default to0.dp; apps using those slots must supply suitable values.
Typography slot taxonomy¶
AppTypography exposes four groups:
- Original 4-slot scale —
display,title,body,label. Kept for back-compat with the library's first cut. - Material 3 standard scale —
displayLarge/Medium/Small,headlineLarge/Medium/Small,titleLarge/Medium/Small,bodyLarge/Medium/Small,labelLarge/Medium/Small. These also flow intoMaterialTheme.typography. - Custom extensions —
subtitle,bodyMonospaced,captionMonospaced,displayRounded,iconSmall/Medium/Large/ExtraLarge. - Weight variants —
labelSmallSemibold,labelSmallBold,labelMediumSemibold,labelMediumBold,labelLargeBold,bodySmallMedium,bodySmallSemibold,bodyMediumSemibold,bodyLargeSemibold. Each is its M3 base slot re-cut at a heavier weight for emphasis, and defaults to that slot with onlyfontWeightchanged — size and line height stay on the scale, and a theme overrides only the ones it wants to tune. They come last in theAppTypographyconstructor, after the custom extensions, so every one carries a default. The:samplebrowser has a Typography → Typography weight variants page that renders each variant against its base slot.
Brand overrides¶
val brandLight = AppColors.light().copy(
primary = BrandBlue,
primaryContainer = BrandBlueSubtle,
success = BrandGreen,
overlayHeavy = Color(0xBF000000),
levels = LevelPalette(
listOf(
LevelTier(background = LevelGreen, foreground = Color.White),
LevelTier(background = LevelOrange, foreground = Color.White),
LevelTier(background = LevelPurple, foreground = Color.White),
),
),
)
AppTheme(lightColors = brandLight) {
Text(text = "Content", color = AppTheme.colors.onSurface)
}
Catalog token coverage¶
Showkase discovers the light and dark semantic color tokens plus the default typography scale through annotated catalog entries in :components. Spacing, motion, shapes, and icons remain visible through component previews because Showkase only has first-class token annotations for colors and typography.