Skapa for Android version 3.0 Migration Guide

This guide provides comprehensive instructions for migrating your Android project from Skapa for latest 2.X release (skapa-bom: 2025.07.01) to Skapa for Android 3.0. All breaking changes are documented with before/after code examples and detailed explanations.

Overview

Skapa for Android 3.0 introduces significant improvements to the design system, including:

Release Plan for 3.0

  1. Stable 3.0 release with deprecations at ERROR level (skapa-bom: 2025.11.01)
  2. Clean up release where all code deprecated with ERROR level is removed

Plans for 2.X track

After the first stable release of 3.0 version of Skapa libraries all feature development together with improvements and non-critical bug fixes will stop for 2.X track. Critical fixes and showstopper issues may still be provided. This means you are expected to migrate in order to get new features.

Migration Process

The migration guide is based on migration from the latest 2.X version to 3.0 for all packages. All the Skapa 3.0 libraries depend on Compose BOM 2025.10.0 (Compose 1.9.4, Material3 1.4.0). Ensure your project is compatible with this version.

  1. Make sure you are using the latest stable version of Android Studio. (Android Studio Narwhal 4 Feature Drop | 2025.1.4)
  2. Latest build tools and updated dependencies:
  3. If you haven't been using skapa-bom:2025.07.01 (latest 2.x stable) we recommend to update to this version and go through all current deprecation warnings in your project and address them before starting the migration to skapa-bom:2025.11.01 (3.0 stable).
  4. Sync and Clean your project: Run ./gradlew clean to remove cached issues
  5. Update dependencies to use Skapa BOM version skapa-bom:2025.11.01
  6. Follow the component-specific migration steps below
  7. Test thoroughly after each component migration

Foundation Migration

SkapaTheme

Migration Type: BREAKING CHANGE (ERROR level deprecation)

What Changed:

What to Do:

// Usage of old Material 2 SkapaTheme. Deprecated with ERROR level
@Composable
fun ExampleScreen() {
    SkapaTheme(darkTheme = true) {
        // Your content
    }
}

// Usage of old Material 3 SkapaTheme. Deprecated with WARNING level
@Composable
fun ExampleScreen() {
    SkapaThemeM3(darkTheme = true) {
        // Your content
    }
}

// Usage of new SkapaTheme. No deprecation.
@Composable
fun ExampleScreen() {
    SkapaTheme2(darkTheme = true) {
        // Your content
    }
}

Action Required:

  1. Replace all SkapaTheme with SkapaTheme2
  2. Replace SkapaThemeM3 with SkapaTheme2 if you do not need the old typography system
  3. Configure theme parameters as needed
  4. Ensure you are using correct SkapaTypeScale configuration depending device size. The default is set to Auto

Typography System

Migration Type: BREAKING CHANGE

What Changed:

Before (Skapa 2.0):

@Composable
fun TypographyExample() {
    // Old typography, using Skapa Typesets
    Text(
        text = "Heading L",
        style = SkapaTheme.typesets.heading.headingL
    )

    // Old typography, using Material 2 mapping system
    Text(
        text = "Heading 1",
        style = SkapaTheme.typography.h1
    )

    // Old typography, using Material 3 mapping system
    Text(
        text = "Headline Large",
        style = SkapaTheme.typographyM3.headlineLarge
    )

    // Latest typography system, using new Skapa typography system
    Text(
        text = "Heading L",
        style = SkapaTheme.typography2.headingL
    )
}

After (Skapa 3.0):

@Composable
fun TypographyExample() {
    // Old typography, using Skapa Typesets
    // Remains unchanged for now but will be deprecated in future release with WARNING level
    Text(
        text = "Heading L",
        style = SkapaTheme.typesets.heading.headingL
    )

    // Old typography, using Material 2 mapping system
    // Note: Now typographyM2 instead of typography
    Text(
        text = "Heading 1",
        style = SkapaTheme.typographyM2.h1
    )

    // Old typography, using Material 3 mapping system
    // Remains unchanged
    Text(
        text = "Headline Large",
        style = SkapaTheme.typographyM3.headlineLarge
    )

    // Latest typography system, using new Skapa typography system
    // Note: Now typography instead of typography2
    Text(
        text = "Heading L",
        style = SkapaTheme.typography.headingL
    )
}

Action Required:

  1. Ensure correct theme wrapper is used at top-level (SkapaTheme2)
  2. Replace all SkapaTheme.typography with SkapaTheme.typographyM2
  3. Replace all SkapaTheme.typography2 with SkapaTheme.typography
  4. Review typography usage and ensure correct styles are applied, some typesets may not map directly and give different visual results

Campaign Sustainability Color Update

Migration Type: BREAKING CHANGE

What Changed:

Before (Skapa 2.0):

@Composable
fun SustainabilityBackgroundExample() {
    Box(modifier = Modifier
        .background(SkapaTheme.colors.campaignSustainability)
        .fillMaxSize()
    ) {
        Text(
            modifier = Modifier.padding(16.dp),
            text = "Sustainability Example",
            color = SkapaTheme.colors.textAndIcon5,
            style = SkapaTheme.typography.bodyM
        )
    }
}

After (Skapa 3.0):

@Composable
fun CheckboxExample() {
    Box(modifier = Modifier
        .background(SkapaTheme.colors.campaignSustainability)
        .fillMaxSize()
    ) {
        Text(
            modifier = Modifier.padding(16.dp),
            text = "Sustainability Example",
            color = SkapaTheme.colors.staticBlack,
            style = SkapaTheme.typography.bodyM
        )
    }
}

Component Migration

Accordion Component

Migration Type: BREAKING CHANGE

What Changed:

Before (Skapa 2.0):

@Composable
fun AccordionExample() {
    Accordion(
        title = "Title",
        caption = "Caption",
        open = isOpen,
        horizontalPadding = 0.dp, // Deprecated
        onClick = {}
    ) { /* Accordion content */ }
}

After (Skapa 3.0):

@Composable
fun AccordionExample() {
    Accordion(
        title = "Title",
        caption = "Caption",
        open = isOpen,
        headerHorizontalPadding = 0.dp, // Use contentHorizontalPadding and headerHorizontalPadding instead.
        contentHorizontalPadding = 0.dp,
        onClick = {}
    ) { /* Accordion content */ }
}

Action Required:

  1. Replace all instances of the old horizontalPadding parameter with headerHorizontalPadding and contentHorizontalPadding.
  2. ReplaceWith suggestions are available for this.

AspectRatioBox component

Migration Type: BREAKING CHANGE (Constructor consolidated)

What Changed:

Before (Skapa 2.0):

@Composable
fun AspectRatioExample() {
    // Separate constructors
    AspectRatioBox(
        aspectRatio = AspectRatio.Square,
        backgroundColor = Color.Blue
    )

    AspectRatioBox(
        aspectRatio = AspectRatio.Widescreen,
        contentAlignment = Alignment.Center
    )
}

After (Skapa 3.0):

@Composable
fun AspectRatioExample() {
    // Unified constructor
    AspectRatioBox(
        aspectRatio = SkapaAspectRatio.Ratio1by1,
        backgroundColor = SkapaTheme.colors.neutral2,
        contentAlignment = Alignment.Center
    )
}

Action Required:

  1. Replace all old aspect ratio objects with SkapaAspectRatio equivalents. For example SkapaAspectRatio.Standard becomes SkapaAspectRatio.Ratio4by3
  2. Update all AspectRatioBox usages to the new unified constructor
  3. Test to ensure aspect ratios and content alignment behave as expected

Button / IconButton component

Migration Type: BREAKING CHANGE

What Changed:

Before (Skapa 2.0):

@Composable
fun ButtonExample() {
    Button(
        variant = ButtonVariant.PrimaryInverse,
        text = "Click me"
    ) {
        /* Click action */
    }

    Button(
        variant = ButtonVariant.SecondaryInverse,
        text = "Click me"
    ) {
        /* Click action */
    }

    Button(
        variant = ButtonVariant.TertiaryInverse,
        text = "Click me"
    ) {
        /* Click action */
    }
}

After (Skapa 3.0):

@Composable
fun ButtonExample() {
    Button(
        variant = ButtonVariant.Primary.Inverse,
        text = "Click me"
    ) {
        /* Click action */
    }

    Button(
        variant = ButtonVariant.Secondary.Inverse,
        text = "Click me"
    ) {
        /* Click action */
    }

    Button(
        variant = ButtonVariant.Tertiary.Inverse,
        text = "Click me"
    ) {
        /* Click action */
    }
}

Card components (Card / CardEmphasised / CardRegular)

Migration Type: BREAKING CHANGE

What Changed:

Using new Card (V2):

@Composable
fun CardExample() {
    Card(
        title = "Title",
        modifier = Modifier.fillMaxWidth(),
        subTitle = "Subtitle",
        body = "Body text",
        variant = CardVariant.Regular,
        titleSize = CardTitleSize.HeadingL,
        cardTheme = CardTheme.Default
    ) {
        /* Click action */
    }
}

Action Required:

  1. Replace all instances of CardEmphasised and CardRegular with Card
  2. Update parameters to match the new Card component, including variant, titleSize, addon, mediaContainer and cardTheme
  3. Update instances of CardV2Variant to CardVariant and CardV2TitleSize to CardTitleSize
  4. Test to ensure card functionality and appearance behave as expected

Carousel component

Migration Type: BREAKING CHANGE

What Changed:

Before (Skapa 2.0):

@Composable
fun CarouselExample() {
    val carouselItems = remember { listOf("Item 1", "Item 2", "Item 3") }

    Carousel(
        variant = CarouselVariant.OverflowWithoutIndicator,
        items = carouselItems
    )
}

After (Skapa 3.0):

@Composable
fun CarouselExample() {
    val carouselItems = remember { listOf("Item 1", "Item 2", "Item 3") }

    Carousel(
        variant = CarouselVariant.Overflow(showIndicator = false),
        items = carouselItems
    )
}

Action Required:

  1. Replace all instances of CarouselVariant.OverflowWithoutIndicator with CarouselVariant.Overflow(showIndicator = false)
  2. Test to ensure carousel functionality and indicators behave as expected

Checkbox / TristateCheckbox component

Migration Type: BREAKING CHANGE

What Changed:

Before (Skapa 2.0):

@Composable
fun CheckboxExample() {
    var isChecked by remember { mutableStateOf(false) }
    var checkboxState by remember { mutableStateOf(ToggleableState.Off) }

    Checkbox(
        label = "Checkbox label",
        checked = isChecked,
        onCheckedChange = { isChecked = it },
        error = true,
        helperText = "This is helper text with error state"
    )

    TristateCheckbox(
        label = "Checkbox label",
        state = checkboxState,
        onClick = { /* handle click */ },
        error = false,
        helperText = "This is optional helper text without error state"
    )
}

After (Skapa 3.0):

@Composable
fun CheckboxExample() {
    var isChecked by remember { mutableStateOf(false) }
    var checkboxState by remember { mutableStateOf(ToggleableState.Off) }

    Checkbox(
        label = "Checkbox label",
        checked = isChecked,
        onCheckedChange = { isChecked = it },
        caption = "Checkbox caption",
        helperTextErrorLabel = "This is an error helper text"
    )

    TristateCheckbox(
        label = "Tristate checkbox label",
        state = checkboxState,
        onClick = { /* handle click */ },
        caption = "Tristate checkbox caption",
        helperTextErrorLabel = "This is an error helper text"
    )
}

Divider component

Migration Type: BREAKING CHANGE

What Changed:

@Composable
fun DividerExample() {
    Divider() // Default horizontal orientation, no parameters available

    Divider(orientation = Orientation.Horizontal)

    Divider(orientation = Orientation.Vertical)
}

DualButton component

Migration Type: BREAKING CHANGE

What Changed:

@Composable
fun DualButtonExample() {
    DualButton(
        orientation = Orientation.Horizontal,
        primaryButton = {
            Button(onClick = {}) {
                Text("Primary")
            }
        },
        secondaryButton = {
            Button(onClick = {}) {
                Text("Secondary")
            }
        }
    )

    DualButton(
        orientation = Orientation.Vertical,
        primaryButton = {
            Button(onClick = {}) {
                Text("Primary")
            }
        },
        secondaryButton = {
            Button(onClick = {}) {
                Text("Secondary")
            }
        }
    )
}

InputField component

Migration Type: BREAKING CHANGE

What Changed:

@Composable
fun InputFieldExample() {
    var inputValue by remember { mutableStateOf("") }

    InputField(
        value = inputValue,
        onValueChange = { inputValue = it },
        label = "Enter text",
        // New enhanced parameters available
        enabledState = EnabledState.Enabled,
        helperTextParams = HelperTextParams(BaseField.State.Default.toHelperState(), label = "Help Text"),
    )
}

Action Required:

  1. Update parameters to use new constructors
  2. Test to ensure input field functionality behaves as expected

ModalHeaderParams / BottomSheet ( Material 2 ) to ModalHeader / Sheet ( Material 3 ) component

Migration Type: BREAKING CHANGE

What Changed:

New Sheet is based on the Material 3 ModalBottomSheet component and has been refactored to align more closely with it Material 3 ModalBottomSheet examples and tutorials can be used as reference for the new Sheet component

@Composable
fun SheetExample() {
    val open = mutableStateOf(true)
    val topPadding = WindowInsets.statusBars.asPaddingValues().calculateTopPadding()

    if (open.value) {
        Sheet(
            onDismiss = { open.value = false },
            modifier = Modifier.padding(top = topPadding),
            sheetState = rememberModalBottomSheetState(),
            modalHeader = ModalHeader.Regular( /* Set modal header parameters */ ), // This is the major change
            sheetFooterParams = ModalsActionFooterParams( /* Set footer parameters */ ),
            content = { /* Sheet content */ }
        )
    }
}

Action Required:

  1. Replace all usage of BottomSheet (M2) with Sheet
  2. Replace all usage of BottomSheetM3 with Sheet
  3. Replace all usage of ModalHeaderParams with ModalHeader
  4. Update state management to handle the new Sheets open and close logic

Pill Component

Migration Type: BREAKING CHANGE (Constructor function removed)

NOTE: It is important to know that due to similar parameters that do not have defaults in the old constructors, to fully access the new constructor you may need to add null for optional parameters. This will be temporarily required until the old constructors are removed in a future release

What Changed:

@Composable
fun PillExample() {
    var pillState by remember { mutableStateOf(true) }
    Pill(
        text = "Label",
        selected = pillState,
        leadingItem = null,
        trailingIconId = R.drawable.icon_sample,
        badgeValue = 5
    ) {
        pillState = !pillState
    }
}

@Composable
fun PillExample() {
    var pillState by remember { mutableStateOf(true) }
    Pill(
        text = "Label",
        selected = pillState,
        leadingItem = null,
        trailingIconId = null,
        badgeValue = null
    ) {
        pillState = !pillState
    }
}

Action Required:

  1. Review all Pill component usage
  2. Update to use the new single constructor, use the replaceWith feature if needed
  3. You may need to add null for optional parameters due to conflicting function signatures with the old deprecated constructors, this will be temporarily required until the old constructors are removed in a future release
  4. Test thoroughly to ensure visual consistency

Price component

Migration Type: BREAKING CHANGE

What Changed:

Before (Skapa 2.0):

@Composable
fun PriceExample() {
    // PriceSizeStyle and Regular variant
    Price(
        integerValue = integerValue,
        decimalValue = decimalValue,
        currency = currency,
        variant = PriceVariant.Regular(subtle = false, regularFontWeight = false),
        subscriptLabel = label,
        priceSizeStyle = PriceSizeStyle.MixedSize
    )
    // Comparison
    Price(
        integerValue = integerValue,
        decimalValue = decimalValue,
        currency = currency,
        variant = PriceVariant.Comparison(strikeout = true, subtle = false, regularFontWeight = false),
        subscriptLabel = label
    )
}

After (Skapa 3.0):

@Composable
fun PriceExample() {
    // PriceSizeStyle and Regular variant
    Price(
        integerValue = integerValue,
        decimalValue = decimalValue,
        currency = currency,
        variant = PriceVariant.Regular(style = PriceVariantStyle.Emphasised, priceSizeStyle = PriceSizeStyle.MixedSize),
        subscriptLabel = label
    )
    // Comparison
    Price(
        integerValue = integerValue,
        decimalValue = decimalValue,
        currency = currency,
        variant = PriceVariant.Comparison(style = PriceVariantStyle.Emphasised),
        subscriptLabel = label
    )
}

Action Required:

  1. Replace all usage of white or light/dark mode dependent foreground colors on campaignSustainability background with SkapaTheme.colors.staticBlack or another color that meets accessibility requirements.
  2. Ensure that the constructor parameters are updated to match the new structure

Price Module component

Migration Type: BREAKING CHANGE

What Changed:

Before (Skapa 2.0):

@Composable
fun PriceModuleExample() {
    PriceModule(
        currentPriceParams = PriceParams( /* price parameters */ ),
        mergeAllDescendants = true
    )
}

After (Skapa 3.0):

@Composable
fun PriceModuleExample() {
    PriceModule(
        currentPriceParams = PriceParams( /* price parameters */ ),
        mergeDescendantsStrategy = MergeDescendantsStrategy.All // Read the KDoc for use-cases
    )
}

Action Required:

  1. Replace all instances of mergeAllDescendants with mergeDescendantsStrategy
  2. Ensure the order of parameters in PriceParams is correct.
  3. Add a value for captionPrefix as the default value has been removed to ensure a string is added and that Talkback reads it correctly.
  4. Test to ensure keyboard navigation and Talkback works as expected

Segmented Control component

Migration Type: BREAKING CHANGE

What Changed:

Before (Skapa 2.0):

@Composable
fun SegmentedControlExample() {
    var selectedLabel by remember { mutableStateOf("x") }

        // List of items of text type
        val itemsLabel = listOf(
            SegmentedControlItem.Text("x", "Label x"),
            SegmentedControlItem.Text("y", "Label y"),
            SegmentedControlItem.Text("z", "Label z")
        )

    SegmentedControl(
        textItems = itemsLabel,
        size = SegmentedControlTextItemSize.Medium, // Old sizing for text and icon items
        selectedKey = selectedLabel,
        onSelect = { selectedLabel = it }
    )
}

After (Skapa 3.0):

@Composable
fun SegmentedControlExample() {
    var selectedLabel by remember { mutableStateOf("x") }

    // List of items of text type
    val itemsLabel = listOf(
        SegmentedControlItem.Text("x", "Label x"),
        SegmentedControlItem.Text("y", "Label y"),
        SegmentedControlItem.Text("z", "Label z")
    )

    SegmentedControl(
        textItems = itemsLabel,
        size = SegmentedControlSize.Text.Small, // Updated sizing for text and icon items
        selectedKey = selectedLabel,
        onSelect = { selectedLabel = it }
    )
}

Action Required:

  1. Replace all instances of SegmentedControlTextItemSize with SegmentedControlSize.Text
  2. Replace all instances of SegmentedControlIconItemSize with SegmentedControlSize.Icon
  3. Validate that your chosen sizes are correct, our mapping has changed

Simple Video component

Migration Type: BREAKING CHANGE

What Changed:

Before (Skapa 2.0):

@Composable
fun SimpleVideoExample() {
    val videoUrl = "https://www.ikea.com/pvid/0823552_fe001096.mp4"
    val playState = remember { mutableStateOf(false) }
    SimpleVideo(
        aspectRatio = SkapaAspectRatio.Ratio16by9,
        player = { VideoPlayer(videoUrl = videoUrl, isPlaying = playState.value) },
        contentDescription = "Video container description",
        buttonAlignment = VideoButtonAlignment.BottomTrailing,
        playButtonParams = VideoPlayerButtonParams.PlayButtonParams(
            contentDescription = "Play / pause video",
            onClick = { state ->
                playState.value = !state // This logic has been refactored
            }
        ),
        fullScreenParams = VideoPlayerButtonParams.FullScreenParams(
            contentDescription = "Full screen",
            onClick = { }
        ),
        transcriptionParams = VideoPlayerButtonParams.TranscriptionParams(
            contentDescription = "Transcription",
            onClick = { }
        ),
        isPlaying = playState, // This has been removed
        onBackgroundPress = null // This has been renamed
    )
}

After (Skapa 3.0):

@Composable
fun SimpleVideoExample() {
    val videoUrl = "https://www.ikea.com/pvid/0823552_fe001096.mp4"
    val playState = remember { mutableStateOf(false) }
    SimpleVideo(
        aspectRatio = SkapaAspectRatio.Ratio16by9,
        player = { VideoPlayer(videoUrl = videoUrl, isPlaying = playState.value) },
        contentDescription = "Video container description",
        buttonAlignment = VideoButtonAlignment.BottomTrailing,
        playButtonParams = VideoPlayerButtonParams.PlayButtonParams(
            isPlaying = playState.value, // Play state has been moved here
            contentDescription = "Play / pause video",
            onClick = { playState.value = !playState.value }
        ),
        fullScreenParams = VideoPlayerButtonParams.FullScreenParams(
            contentDescription = "Full screen",
            onClick = { }
        ),
        transcriptionParams = VideoPlayerButtonParams.TranscriptionParams(
            contentDescription = "Transcription",
            onClick = { }
        ),
        onBackgroundClick = { playState.value = !playState.value }
    )
}

Action Required:

  1. Move all instances of isPlaying to VideoPlayerButtonParams.PlayButtonParams
  2. Replace all instances of onBackgroundPress with onBackgroundClick
  3. Validate that your video player is working as expected

TextArea component

Migration Type: BREAKING CHANGE

What Changed:

Before (Skapa 2.0):

@Composable
fun TextAreaExample() {
    var textValue by remember { mutableStateOf("") }

    TextArea(
        value = textValue,
        onValueChange = { textValue = it },
        enabled = true
    )
}

After (Skapa 3.0):

@Composable
fun TextAreaExample() {
    var textValue by remember { mutableStateOf("") }

    TextArea(
        value = textValue,
        onValueChange = { textValue = it },
        enabledState = EnabledState.Enabled
    )

    // For disabled state
    TextArea(
        value = textValue,
        onValueChange = { textValue = it },
        enabledState = EnabledState.Disabled
    )

    // For read-only state
    TextArea(
        value = textValue,
        onValueChange = { textValue = it },
        enabledState = EnabledState.ReadOnly
    )
}

Action Required:

  1. Replace all instances of enabled: Boolean with enabledState: EnabledState
  2. Use EnabledState.Enabled, EnabledState.Disabled, or EnabledState.ReadOnly as appropriate
  3. Test to ensure text area functionality behaves as expected

Post-Migration Steps

1. Clean Build

After completing the migration, clean your project to remove cached issues:

./gradlew clean

2. Build and Test

Compile your project to ensure all changes are applied correctly:

./gradlew build

3. Run Tests

Execute your test suite to verify functionality:

./gradlew test

4. Visual Testing

Perform thorough visual testing to ensure UI components render correctly with the new design system.


Common Issues and Solutions

Issue: Compilation Errors After Migration

Solution:

  1. Perform a clean build: ./gradlew clean
  2. Invalidate caches in Android Studio
  3. Ensure all deprecated APIs have been replaced
  4. Check for any lingering errors in the IDE

Issue: Typography Not Displaying Correctly

Solution:

  1. Verify you're using SkapaTheme2 wrapper
  2. Check typography references are updated correctly
  3. Ensure proper theme application at app level

Issue: Component Layout Issues

Solution:

  1. Review parameter changes for affected components
  2. Test with different screen sizes and orientations
  3. Verify accessibility features still work correctly

Additional Resources


Support

If you encounter issues during migration:

  1. Check this guide for component-specific solutions
  2. Review the changelog for detailed technical changes
  3. Contact the Skapa team for additional support

Contacts

Last Updated: September 04, 2025 Guide Version: 1.0 Target Skapa Version: 3.0 Target Skapa-BOM Version: 2025.09.00-alpha01