When building cross-platform apps with Compose Multiplatform, sharing user interface layout code is only half the battle. To create a cohesive design, you also need to share static assets—such as vector graphics, localized string resources, and custom branding typography.
Historically, sharing resources across Android (which uses XML drawables) and iOS (which uses Asset Catalogs) required complex Gradle plugins or manual platform-specific wrappers.
With recent JetBrains Compose updates, there is a built-in Compose Multiplatform Resource Library that handles resources natively using code-generation. This guide details how to configure and utilize this tool to share images, fonts, and localized strings.
1. Setting Up the Resource Library
The Compose Multiplatform Resource library is packaged directly with JetBrains Compose. To enable code-generation for resources, configure the Multiplatform Gradle plugin in shared/build.gradle.kts:
plugins {
alias(libs.plugins.kotlinMultiplatform)
alias(libs.plugins.jetbrainsCompose)
alias(libs.plugins.compose.compiler)
}
kotlin {
// Target configurations (android, ios, etc.)
sourceSets {
commonMain.dependencies {
implementation(compose.runtime)
implementation(compose.foundation)
implementation(compose.material3)
// Core resource library dependency
implementation(compose.components.resources)
}
}
}
Once synced, the compiler will automatically scan your resource directories and generate a Res class, which serves as the type-safe access point.
2. Resource Directory Structure
All shared resources must live within the commonMain/composeResources directory. The subdirectory names are strictly defined:
shared/src/commonMain/composeResources/
├── drawable/
│ ├── ic_logo.xml (Vector drawable)
│ └── hero_banner.png
├── font/
│ ├── outfit_regular.ttf
│ └── outfit_bold.ttf
├── values/
│ └── strings.xml (Default strings)
└── values-fr/
└── strings.xml (French localization)
3. Loading Vector and Raster Images
You can place both SVG/XML vector graphics and standard PNG/JPEG images inside the drawable folder. The library generates type-safe references under Res.drawable:
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import org.jetbrains.compose.resources.painterResource
import myproject.shared.generated.resources.Res
import myproject.shared.generated.resources.ic_logo
@Composable
fun AppLogo() {
Image(
painter = painterResource(Res.drawable.ic_logo),
contentDescription = "App Logo",
modifier = Modifier.size(64.dp)
)
}
4. Localizing Strings
To support multiple languages, define a default strings.xml in values/ and language-specific keys in directories like values-fr/ or values-es/.
Default string value (values/strings.xml):
<resources>
<string name="welcome_message">Hello, Welcome back!</string>
<string name="user_profile">User profile: %s</string>
</resources>
Loading strings in Composables:
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import org.jetbrains.compose.resources.stringResource
import myproject.shared.generated.resources.Res
import myproject.shared.generated.resources.welcome_message
import myproject.shared.generated.resources.user_profile
@Composable
fun DashboardHeader(userName: String) {
Text(
text = stringResource(Res.string.welcome_message)
)
// Supporting string formatting
Text(
text = stringResource(Res.string.user_profile, userName)
)
}
5. Adding Custom Branding Fonts
Branding typography is key to premium UI. Place your .ttf or .otf files in the font directory, load them with Font(), and combine them into a FontFamily:
import androidx.compose.material3.Typography
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import org.jetbrains.compose.resources.Font
import myproject.shared.generated.resources.Res
import myproject.shared.generated.resources.outfit_regular
import myproject.shared.generated.resources.outfit_bold
@Composable
fun getAppTypography(): Typography {
val outfitFamily = FontFamily(
Font(Res.font.outfit_regular, FontWeight.Normal),
Font(Res.font.outfit_bold, FontWeight.Bold)
)
return Typography(
// Configure styles using custom FontFamily
)
}
Conclusion
The Compose Multiplatform Resource library removes the final roadblock to writing 100% shared UI. By generating type-safe resources, it guarantees compiled validation and prevents runtime file-not-found crashes on both Android and iOS.
To understand how to combine this shared UI resource logic with unified persistence layers, read my guides on Database Persistence using SQLDelight and Shared API client configurations in KMP.
Looking for a Compose Multiplatform UI Expert?
Are you designing a shared mobile codebase or want to translate custom brand experiences across iOS and Android natively? I specialize in building custom, high-performance UI components with shared assets in Compose Multiplatform. I work as a remote contractor for engineering teams in Europe, the UK, and Canada, offering seamless timezone coverage.