| name | styles |
| description | 使用此 skill 将 Jetpack Compose Styles API 集成到 Android 项目。引导你升级依赖、设置组件主题、让自定义组件可样式化,以及将现有布局属性迁移到统一样式。迁移自定义设计系统组件、用 Style 属性替换硬编码参数、使用 Modifier.styleable 处理交互状态。 |
| license | Complete terms in LICENSE.txt |
| metadata | {"author":"Google LLC","last-updated":"2026-07-07","keywords":["Jetpack Compose","Styles","Theming with Styles","Migrate to Styles","Modifier.styleable"]} |
Limitations
- Warn the user that this skill is EXPERIMENTAL and requires updating to alpha version of Compose and opting in to the Experimental APIs.
- This skill only supports custom UI components and custom themes.
- This skill does not support Material Design component Styles.
Prerequisites
1. Upgrade dependencies
- The project must use
compileSdk version 37 or higher.
- The project must use
androidx.compose.foundation:foundation version 1.12.0-alpha01 or higher.
- Alternatively, the project must use Compose BOM version
2026.04.01 or higher.
- The API requires this exact package:
import androidx.compose.foundation.style.Style
2. Configure compiler options to enable experimental API
You must opt-in to the experimental API at the project level. Add the following
block to your module's build.gradle.kts:
kotlin {
compilerOptions {
jvmTarget = JvmTarget.fromTarget("17")
freeCompilerArgs.add("-opt-in=androidx.compose.foundation.style.ExperimentalFoundationStyleApi")
}
}
Core workflows and guides
Refer to the official documentation to complete specific development tasks:
Step-by-Step Migration Workflow
Step 1: Analyze theme structure
- Locate your central theme file (such as
Theme.kt).
- Identify design tokens. Note references for colors, typography, and shapes (for example,
LocalColorScheme, LocalTypography, or LocalShapes).
- If the project lacks Jetpack Compose dependencies, stop. Instruct the user to migrate to Jetpack Compose first.
- If the project imports
androidx.compose.material.MaterialTheme, recommend migrating to Material 3 before proceeding.
Step 2: Establish ComponentStyles
-
Create a new file named ComponentStyles.kt in your theme directory.
-
Define a top-level data class to hold your component styles, for example, the Jetsnack one is called JetsnackStyles:
object ExampleComponentStyles {
val customButtonStyle: Style = {
}
val customTextFieldStyle: Style = {
}
}
-
Expose this class through your custom theme with a static reference, don't
use CompositionLocals here as it's not required.
@Immutable
class JetsnackTheme(
) {
companion object {
val colors: CustomThemingWithStyles.JetsnackColors
@Composable @ReadOnlyComposable
get() = LocalJetsnackTheme.current.colors
val styles: ComponentStyles = ComponentStyles
}
}
-
Provide extensions on StyleScope to reference theme tokens directly if
they are exposed using CompositionLocals. For example:
val StyleScope.colors: JetsnackColors
get() = LocalJetsnackTheme.currentValue.colors
val StyleScope.typography: androidx.compose.material3.Typography
get() = LocalJetsnackTheme.currentValue.typography
val StyleScope.shapes: Shapes
get() = LocalJetsnackTheme.currentValue.shapes
Step 3: Migrate a component to Styles API
For each custom component (for example, CustomButton), complete the following
sequence:
- Establish a visual baseline (If an emulator is available):
- If you CANNOT run an Android emulator: Skip this step entirely and proceed to Step 2.
- If you CAN run an Android emulator: Perform the following to capture a baseline screenshot:
- Option A: Locate and run an existing screenshot test for the component.
- Option B (If no test exists): Create a test using the project's existing testing framework, then run it.
- Option C (If no framework exists): Create a minimal screenshot test using UI Automator or Espresso, then run it.
- Remove individual styling parameters : Remove styling parameters such as
backgroundColor, shape, textStyle, and contentPadding from the signature - anything that StyleScope supports.
- Add the style parameter : Add
style: Style = Style to the function signature. Always ensure the default value is exactly Style (e.g., style: Style = Style) and not a specific style default like ChipStyleDefault or any other value.
- Declare state tracking : If the component is interactable, create a
MutableStyleState using the interaction source. Update state fields (such as isEnabled) inside the Composable to track the state correctly.
- Apply styleable modifier : Replace specific layout modifiers on the root element with
Modifier.styleable().
- Move defaults to ComponentStyles : Move hardcoded values from the component definition to a dedicated
Style instance in ComponentStyles.kt.
- Validate component: Compare the baseline screenshot image taken at the start with the rendered Compose Preview of the new composable. Ignore string content; focus on layout and styling. Iterate on the Compose code until visual parity is achieved. Once verified, write a Compose UI test for the new composable.
Migration example
Before Migration:
@Composable
fun CustomButton(
onClick: () -> Unit,
modifier: Modifier = Modifier,
backgroundColor: Color = JetsnackTheme.colors.brandLight,
disabledBackgroundColor: Color = JetsnackTheme.colors.brandSecondary,
shape: Shape = JetsnackTheme.shapes.extraLarge,
textStyle: TextStyle = JetsnackTheme.typography.labelLarge,
enabled: Boolean = true,
content: @Composable RowScope.() -> Unit,
) {
val interactionSource = remember { MutableInteractionSource() }
Row(
modifier
.clickable(onClick = onClick, indication = null, interactionSource = interactionSource)
.background(if (enabled) backgroundColor else disabledBackgroundColor, shape)
.defaultMinSize(58.dp, 40.dp),
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically,
content = content,
)
}
After Migration:
object ComponentStyles {
val buttonStyle = Style {
background(colors.brandLight)
shape(shapes.extraLarge)
minWidth(58.dp)
minHeight(40.dp)
textStyle(typography.labelLarge)
disabled {
background(colors.brandSecondary)
}
}
}
@Composable
fun CustomButton(
onClick: () -> Unit,
modifier: Modifier = Modifier,
style: Style = Style,
enabled: Boolean = true,
content: @Composable RowScope.() -> Unit,
) {
val interactionSource = remember { MutableInteractionSource() }
val styleState = rememberUpdatedStyleState(interactionSource) {
it.isEnabled = enabled
}
Row(
modifier
.clickable(onClick = onClick, indication = null, interactionSource = interactionSource)
.styleable(styleState, JetsnackTheme.styles.buttonStyle, style),
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically,
content = content,
)
}
Step 4: Validate Changes
- Build the project. Verify that there are no compilation errors.
- Run your module's screenshot tests.
- Compare visual outputs of the whole app between the previous and updated components. Verify that no visual layout regressions occur.