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:
- Basic Style Usage: To set backgrounds, sizes, and alignments on a component, follow the Compose Styles Fundamentals
Guide.
- State and Transitions: To configure property changes for state shifts (like pressed or hovered), follow the Animations and State-Based Styling
Guide.
- Architecture Trade offs: To decide when to use a Style versus a standard Modifier, follow the Styles versus Modifiers
Comparison.
- Theme Level Integration: To connect style definitions with custom themes, follow Theming with Styles and Custom Themes in Compose.
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(
// other Design system properties
) {
companion object {
val colors: CustomThemingWithStyles.JetsnackColors
@Composable @ReadOnlyComposable
get() = LocalJetsnackTheme.current.colors
// ...
// add helper static reference
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:
- If you are able to run an Android emulator, locate an existing screenshot test for the component. If none exists, create one using the existing project testing framework. If no framework exists, use UI Automator or Espresso to create a screenshot test with minimum required setup. Run the test and take a baseline screenshot of the Component. ELSE proceed to the next step without a screenshot test.
- 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.
- 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:
// Exposed via ComponentStyles.kt
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.
1---2name: styles3description: Migrate Android Jetpack Compose UI components to use the experimental Styles API for centralized theming and state-based styling.4license: Complete terms in LICENSE.txt5---67## Limitations89- Warn the user that this skill is EXPERIMENTAL and requires updating to alpha version of Compose and opting in to the Experimental APIs.10- This skill only supports custom UI components and custom themes.11- This skill does not support Material Design component Styles.1213## Prerequisites1415### 1. Upgrade dependencies1617- The project must use `compileSdk` version 37 or higher.18- The project must use `androidx.compose.foundation:foundation` version `1.12.0-alpha01` or higher.19- Alternatively, the project must use Compose BOM version `2026.04.01` or higher.20- The API requires this exact package: `import21 androidx.compose.foundation.style.Style`2223### 2. Configure compiler options to enable experimental API2425You must opt-in to the experimental API at the project level. Add the following26block to your module's `build.gradle.kts`:2728 kotlin {29 compilerOptions {30 jvmTarget = JvmTarget.fromTarget("17")31 freeCompilerArgs.add("-opt-in=androidx.compose.foundation.style.ExperimentalFoundationStyleApi")32 }33 }3435## Core workflows and guides3637Refer to the official documentation to complete specific development tasks:3839- Basic Style Usage: To set backgrounds, sizes, and alignments on a component, follow the [Compose Styles Fundamentals40 Guide](references/android/develop/ui/compose/styles/fundamentals.md).41- State and Transitions: To configure property changes for state shifts (like pressed or hovered), follow the [Animations and State-Based Styling42 Guide](references/android/develop/ui/compose/styles/state-animations.md).43- Architecture Trade offs: To decide when to use a Style versus a standard Modifier, follow the [Styles versus Modifiers44 Comparison](references/android/develop/ui/compose/styles/styles-vs-modifiers.md).45- Theme Level Integration: To connect style definitions with custom themes, follow [Theming with Styles](references/android/develop/ui/compose/styles/theming.md) and [Custom Themes in Compose](references/android/develop/ui/compose/designsystems/custom.md).4647## Step-by-Step Migration Workflow4849### Step 1: Analyze theme structure50511. Locate your central theme file (such as `Theme.kt`).522. Identify design tokens. Note references for colors, typography, and shapes (for example, `LocalColorScheme`, `LocalTypography`, or `LocalShapes`).533. If the project lacks Jetpack Compose dependencies, stop. Instruct the user to migrate to Jetpack Compose first.544. If the project imports `androidx.compose.material.MaterialTheme`, recommend migrating to Material 3 before proceeding.5556### Step 2: Establish `ComponentStyles`57581. Create a new file named `ComponentStyles.kt` in your theme directory.592. Define a top-level data class to hold your component styles, for example, the Jetsnack one is called `JetsnackStyles`:606162 ```kotlin63 object ExampleComponentStyles {64 val customButtonStyle: Style = {6566 }67 val customTextFieldStyle: Style = {6869 }70 }71 ```7273 <br />74753. Expose this class through your custom theme with a static reference, don't76 use `CompositionLocals` here as it's not required.777879 ```kotlin80 @Immutable81 class JetsnackTheme(82 // other Design system properties83 ) {84 companion object {85 val colors: CustomThemingWithStyles.JetsnackColors86 @Composable @ReadOnlyComposable87 get() = LocalJetsnackTheme.current.colors88 // ...8990 // add helper static reference91 val styles: ComponentStyles = ComponentStyles92 }93 }94 ```9596 <br />97984. Provide extensions on `StyleScope` to reference theme tokens directly if99 they are exposed using `CompositionLocals`. For example:100101102 ```kotlin103 val StyleScope.colors: JetsnackColors104 get() = LocalJetsnackTheme.currentValue.colors105106 val StyleScope.typography: androidx.compose.material3.Typography107 get() = LocalJetsnackTheme.currentValue.typography108109 val StyleScope.shapes: Shapes110 get() = LocalJetsnackTheme.currentValue.shapes111 ```112113 <br />114115### Step 3: Migrate a component to Styles API116117For each custom component (for example, `CustomButton`), complete the following118sequence:1191201. If you are able to run an Android emulator, locate an existing screenshot test for the component. If none exists, create one using the existing project testing framework. If no framework exists, use UI Automator or Espresso to create a screenshot test with minimum required setup. Run the test and take a baseline screenshot of the Component. ELSE proceed to the next step without a screenshot test.1212. **Remove individual styling parameters** : Remove styling parameters such as `backgroundColor`, `shape`, `textStyle`, and `contentPadding` from the signature - anything that `StyleScope` supports.1223. **Add the style parameter** : Add `style: Style = Style` to the function signature.1234. **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.1245. **Apply styleable modifier** : Replace specific layout modifiers on the root element with `Modifier.styleable()`.1256. **Move defaults to ComponentStyles** : Move hardcoded values from the component definition to a dedicated `Style` instance in `ComponentStyles.kt`.1267. **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.127128#### Migration example129130Before Migration:131132133```kotlin134@Composable135fun CustomButton(136 onClick: () -> Unit,137 modifier: Modifier = Modifier,138 backgroundColor: Color = JetsnackTheme.colors.brandLight,139 disabledBackgroundColor: Color = JetsnackTheme.colors.brandSecondary,140 shape: Shape = JetsnackTheme.shapes.extraLarge,141 textStyle: TextStyle = JetsnackTheme.typography.labelLarge,142 enabled: Boolean = true,143 content: @Composable RowScope.() -> Unit,144) {145 val interactionSource = remember { MutableInteractionSource() }146 Row(147 modifier148 .clickable(onClick = onClick, indication = null, interactionSource = interactionSource)149 .background(if (enabled) backgroundColor else disabledBackgroundColor, shape)150 .defaultMinSize(58.dp, 40.dp),151 horizontalArrangement = Arrangement.Center,152 verticalAlignment = Alignment.CenterVertically,153 content = content,154 )155}156```157158<br />159160After Migration:161162163```kotlin164// Exposed via ComponentStyles.kt165object ComponentStyles {166 val buttonStyle = Style {167 background(colors.brandLight)168 shape(shapes.extraLarge)169 minWidth(58.dp)170 minHeight(40.dp)171 textStyle(typography.labelLarge)172 disabled {173 background(colors.brandSecondary)174 }175 }176}177178@Composable179fun CustomButton(180 onClick: () -> Unit,181 modifier: Modifier = Modifier,182 style: Style = Style,183 enabled: Boolean = true,184 content: @Composable RowScope.() -> Unit,185) {186 val interactionSource = remember { MutableInteractionSource() }187 val styleState = rememberUpdatedStyleState(interactionSource) {188 it.isEnabled = enabled189 }190 Row(191 modifier192 .clickable(onClick = onClick, indication = null, interactionSource = interactionSource)193 .styleable(styleState, JetsnackTheme.styles.buttonStyle, style),194 horizontalArrangement = Arrangement.Center,195 verticalAlignment = Alignment.CenterVertically,196 content = content,197 )198}199```200201<br />202203### Step 4: Validate Changes2042051. Build the project. Verify that there are no compilation errors.2062. Run your module's screenshot tests.2073. Compare visual outputs of the whole app between the previous and updated components. Verify that no visual layout regressions occur.