
Dynamic theme management library enabling custom theme families, mode switching, persistence, and previewing with Theme Picker UI. Integrates with Material 3 for adaptable styling.
A Kotlin Multiplatform library for dynamic theme management using Jetpack Compose Multiplatform.
It allows you to:
ThemeStore (works with MultiplatformSettings or custom stores)ThemePickerBottomSheet & sample AppScaffold
Add the dependency to your commonMain:
commonMain.dependencies {
implementation("io.github.iammohdzaki.kmpalette:theme-core:<check-tag-eg.0.0.1>")
}Below is the high-level architecture of the Dynamic Theme Multiplatform Library:
Represents a single theme variant (Light or Dark).
val lightTheme = ThemeDefinition(
id = ThemeId("ocean_light"),
displayName = "Ocean Light",
palette = lightPalette
)A pair of Light/Dark themes grouped under a single name.
val oceanFamily = ThemeFamily(
displayName = "Ocean",
light = oceanLight,
dark = oceanDark
)Registers all available themes.
val registry = DefaultThemeRegistry().apply {
registerFamily(oceanFamily)
registerFamily(forestFamily)
}Manages active theme, system mode, and persistence.
val controller = ThemeController(
registry = registry,
store = SettingsThemeStore(Settings()), // pluggable
system = PlatformSystemThemeProvider(),
defaultThemeId = ThemeId("m3_light")
)Applies theme to your UI (Material 3).
DynamicThemeProvider(
controller = controller,
adapter = Material3Adapter(
typography = CustomTypography(),
shapes = CustomShapes()
)
) {
AppScaffold()
}A ready-to-use Responsive Picker UI for switching themes:
ThemePickerBottomSheet(
onDismiss = { showSheet = false }
)The picker automatically adjusts to provide the best design experience based on the target platform:
It shows:
Implement your own ThemeStore if you don’t want to use MultiplatformSettings:
interface ThemeStore {
suspend fun load(): ThemeSelection?
suspend fun save(selection: ThemeSelection)
}Example: SettingsThemeStore using MultiplatformSettings.
A sample scaffold to showcase theme switching:
@Composable
fun App() {
val controller = remember {
val registry = DefaultThemeRegistry().apply {
registerFamilies(DefaultMaterial3Themes.families)
}
ThemeController(
registry = registry,
store = SettingsThemeStore(Settings()), // You can plug any persistence like Multiplatform Settings,DataStore etc.
system = PlatformSystemThemeProvider(),
defaultThemeId = ThemeId("m3_light")
)
}
DynamicThemeProvider(
controller = controller,
adapter = Material3Adapter(),
typography = SansTypography()
) {
AppScaffold()
}
}Instead of mapping colors manually, use the boilerplate-reducing helper extension functions in your source code:
ColorScheme.toThemeDefinition(...) converts a standard Compose ColorScheme to a ThemeDefinition.createThemeFamily(...) builds a paired Light/Dark ThemeFamily in a single line:val myFamily = createThemeFamily(
id = "neon_city",
displayName = "Neon City",
lightScheme = neonLightColorScheme,
darkScheme = neonDarkColorScheme
)
registry.registerFamily(myFamily)The flexible ComposeThemeAdapter structure makes the library 100% future-proof. For example, if you want to support experimental expressive motion and styling features in newer Compose versions, you can implement a custom adapter in your app:
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
class ExpressiveM3Adapter : ComposeThemeAdapter {
@Composable
override fun MaterialTheme(
theme: ThemeDefinition,
typography: Typography?,
shapes: Shapes?,
content: @Composable () -> Unit
) {
val colors = ColorScheme( /* Map theme.palette colors... */ )
MaterialExpressiveTheme(
colorScheme = colors,
typography = typography ?: MaterialTheme.typography,
shapes = shapes ?: MaterialTheme.shapes,
content = content
)
}
}And pass it directly to DynamicThemeProvider(adapter = ExpressiveM3Adapter()).
If you are using an AI coding assistant (such as GitHub Copilot, Cursor, or Gemini) to integrate this library, you can refer it to the SKILL.md file at the root of the repository. It contains structured instructions, design guidelines, and code snippets formatted for AI context injection.
A Kotlin Multiplatform library for dynamic theme management using Jetpack Compose Multiplatform.
It allows you to:
ThemeStore (works with MultiplatformSettings or custom stores)ThemePickerBottomSheet & sample AppScaffold
Add the dependency to your commonMain:
commonMain.dependencies {
implementation("io.github.iammohdzaki.kmpalette:theme-core:<check-tag-eg.0.0.1>")
}Below is the high-level architecture of the Dynamic Theme Multiplatform Library:
Represents a single theme variant (Light or Dark).
val lightTheme = ThemeDefinition(
id = ThemeId("ocean_light"),
displayName = "Ocean Light",
palette = lightPalette
)A pair of Light/Dark themes grouped under a single name.
val oceanFamily = ThemeFamily(
displayName = "Ocean",
light = oceanLight,
dark = oceanDark
)Registers all available themes.
val registry = DefaultThemeRegistry().apply {
registerFamily(oceanFamily)
registerFamily(forestFamily)
}Manages active theme, system mode, and persistence.
val controller = ThemeController(
registry = registry,
store = SettingsThemeStore(Settings()), // pluggable
system = PlatformSystemThemeProvider(),
defaultThemeId = ThemeId("m3_light")
)Applies theme to your UI (Material 3).
DynamicThemeProvider(
controller = controller,
adapter = Material3Adapter(
typography = CustomTypography(),
shapes = CustomShapes()
)
) {
AppScaffold()
}A ready-to-use Responsive Picker UI for switching themes:
ThemePickerBottomSheet(
onDismiss = { showSheet = false }
)The picker automatically adjusts to provide the best design experience based on the target platform:
It shows:
Implement your own ThemeStore if you don’t want to use MultiplatformSettings:
interface ThemeStore {
suspend fun load(): ThemeSelection?
suspend fun save(selection: ThemeSelection)
}Example: SettingsThemeStore using MultiplatformSettings.
A sample scaffold to showcase theme switching:
@Composable
fun App() {
val controller = remember {
val registry = DefaultThemeRegistry().apply {
registerFamilies(DefaultMaterial3Themes.families)
}
ThemeController(
registry = registry,
store = SettingsThemeStore(Settings()), // You can plug any persistence like Multiplatform Settings,DataStore etc.
system = PlatformSystemThemeProvider(),
defaultThemeId = ThemeId("m3_light")
)
}
DynamicThemeProvider(
controller = controller,
adapter = Material3Adapter(),
typography = SansTypography()
) {
AppScaffold()
}
}Instead of mapping colors manually, use the boilerplate-reducing helper extension functions in your source code:
ColorScheme.toThemeDefinition(...) converts a standard Compose ColorScheme to a ThemeDefinition.createThemeFamily(...) builds a paired Light/Dark ThemeFamily in a single line:val myFamily = createThemeFamily(
id = "neon_city",
displayName = "Neon City",
lightScheme = neonLightColorScheme,
darkScheme = neonDarkColorScheme
)
registry.registerFamily(myFamily)The flexible ComposeThemeAdapter structure makes the library 100% future-proof. For example, if you want to support experimental expressive motion and styling features in newer Compose versions, you can implement a custom adapter in your app:
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
class ExpressiveM3Adapter : ComposeThemeAdapter {
@Composable
override fun MaterialTheme(
theme: ThemeDefinition,
typography: Typography?,
shapes: Shapes?,
content: @Composable () -> Unit
) {
val colors = ColorScheme( /* Map theme.palette colors... */ )
MaterialExpressiveTheme(
colorScheme = colors,
typography = typography ?: MaterialTheme.typography,
shapes = shapes ?: MaterialTheme.shapes,
content = content
)
}
}And pass it directly to DynamicThemeProvider(adapter = ExpressiveM3Adapter()).
If you are using an AI coding assistant (such as GitHub Copilot, Cursor, or Gemini) to integrate this library, you can refer it to the SKILL.md file at the root of the repository. It contains structured instructions, design guidelines, and code snippets formatted for AI context injection.