MaterialKolor: a Material 3 color scheme generated from one seed color
🎨 Generate a dynamic Material3 color scheme from a seed color
At a glance
- What is it?
- MaterialKolor runs Google's material-color-utilities inside Compose Multiplatform to turn a single seed color into a full Material scheme, with adapters for Material 3, Compose Unstyled and Compose Fluent. Version 6.0 changes the color specification, and the migration guide is part of the upgrade.
- Who is it for?
- Adopt MaterialKolor when your Compose Multiplatform app needs its palette to follow one brand color on every platform, and you want the role set, the tonal palettes and the contrast handling to come from Google's own color utilities rather than from hand-picked hex values.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 1 day ago.
- What is it written in?
- Mainly Kotlin, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
One seed, a whole scheme, six platforms
MaterialKolor generates Material color schemes from a single seed color in Compose Multiplatform, running Google's material-color-utilities on Android, iOS, macOS, desktop JVM, JS and Wasm.
The decision to build on material-color-utilities rather than on a from-scratch algorithm is the core of the project. The Material 3 colour roles, the tonal palettes underneath them and the contrast handling are all defined by that library, so a scheme produced here is the same scheme the Material specification describes rather than an approximation of it.
The badges in the README pin the stack the project is built against: Kotlin 2.4.21, Compose Multiplatform 1.12.1, and a specific material-color-utilities commit. The platform badges cover Android, iOS, desktop and JS/Wasm.
For a reader, the practical implication is that MaterialKolor is a Compose library first. It is not a command line tool, not a design token generator and not a Figma plugin. Its output is Compose values inside your app, which means it participates in recomposition and in your own theme composition rather than producing a static asset at build time.
The artifact table tells you which module you actually need
Installation is a matter of choosing one artifact, and the README presents the choice as a table because the modules are not interchangeable.
material-kolor-material3 is for Material 3 apps. material-kolor-unstyled is for Compose Unstyled apps. material-kolor-fluent is for Compose Fluent apps. material-kolor-core is for your own theme, or for any app that only needs the colours. material-kolor-palette is for seed colours extracted from images, and it uses the separate kmpalette library for that. material-color-utilities is the colour engine on its own, without Compose.
Each adapter brings in material-kolor-core, so the dependency graph is layered: the engine at the bottom, the core scheme generator above it, and the toolkit-specific adapters on top.
For a Kotlin Multiplatform project the dependency goes into the commonMain source set, which is what the README's snippet shows:
kotlin {
sourceSets {
commonMain.dependencies {
implementation("com.materialkolor:material-kolor-material3:6.0.0")
}
}
}The version catalog form is given as an alternative, using a materialKolor version entry and a library entry keyed to the module com.materialkolor:material-kolor-material3. One caveat the README raises for the Unstyled and Fluent adapters: they also need the toolkit itself declared next to them, and their own READMEs cover the details.
The packages are published to Maven Central, with material-kolor-core as the artifact shown in the badge.
DynamicMaterialTheme and the options every entry point accepts
For a Material 3 app the entry point is DynamicMaterialTheme, which wraps MaterialTheme and generates the colour scheme from a seed:
@Composable
fun AppTheme(seedColor: Color, content: @Composable () -> Unit) {
DynamicMaterialTheme(
seedColor = seedColor,
isDark = isSystemInDarkTheme(),
animate = true,
content = content,
)
}If you would rather call MaterialTheme yourself, rememberDynamicColorScheme returns the ColorScheme inside composition and dynamicColorScheme does the same outside it.
The options are the interesting part, because the same set is accepted by all of them. The style argument picks a PaletteStyle such as TonalSpot, which is the default, or Vibrant, Expressive or Monochrome. The primary, secondary, tertiary, neutral, neutralVariant and error arguments pin that role to an exact colour, and the README is clear that the seed still drives the rest of the palette.
Contrast is a numeric dial: contrastLevel runs from -1.0 to 1.0, with Contrast.Medium.value and Contrast.High.value as the standard steps. isAmoled makes the dark background and surface pure black, which is a dark-theme-only effect.
And specVersion defaults to SPEC_2025. Passing ColorSpec.SpecVersion.SPEC_2021 gives you the colours MaterialKolor produced before 6.0, which is the single most important thing to know if you are upgrading an existing app and do not want the palette to shift.
For Material 3 Expressive there is a separate DynamicMaterialExpressiveTheme, which defaults to PaletteStyle.Expressive and accepts a motionScheme.
Beyond the roles: tonal palettes and a readable on-colour
Material defines a fixed set of roles, and any design that needs a colour outside that set ends up hard-coding a hex value somewhere. MaterialKolor's answer is to expose the tonal palettes the roles are drawn from.
Each scheme carries the six tonal palettes the roles come from, and you can read any tone from 0 to 100 off them. The README suggests using these for the colours Material has no role for, such as a pressed state, a gradient stop or a severity scale:
val pressed = scheme.primaryPalette.toneColor(30)
val badge = scheme.tertiaryPalette.toneColor(90)
val onBadge = scheme.tertiaryPalette.onTone(90)onTone is the part that saves work. It gives you a readable content colour from the same palette, and it aims for WCAG AA by default. Without it, picking a foreground for an arbitrary tone is the step where contrast bugs come from, and the tone that looks right as a background is often unreadable as text.
When you need more accent colours than the scheme contains, rememberTonalPalette builds a palette from any colour, and it can shift its hue towards your seed so the result fits the rest of the theme. The README's example takes a green, harmonises it with the seed, and then reads tone 90 from it as a container colour.
That harmoniseWith argument is the mechanism for extending a palette without the new colour looking bolted on, which is a capability the Material 3 role set alone does not give you.
material-kolor-core has no Material 3 dependency
The core module is the part of this library worth knowing about if your design system is not Material 3.
material-kolor-core does not depend on Material 3. rememberDynamicScheme generates the scheme, and MaterialKolors reads its roles as Compose colours:
val scheme = rememberDynamicScheme(seedColor = seedColor, isDark = isDark)
val kolors = remember(scheme) { MaterialKolors(scheme) }
val accent = kolors.primary()
val onAccent = kolors.onPrimary()That means you can use the generator to drive a theme of your own design, as long as you want the Material algorithm and the Material roles. The samples directory contains a custom-theme project that builds a complete theme this way.
The alternative adapters go further in the other direction. material-kolor-unstyled turns a scheme into Compose Unstyled theme values through a buildThemeV2 block, with light and dark colours produced by rememberDynamicLightDarkColors and assigned into the theme properties for each colour scheme. material-kolor-fluent generates Fluent accent shades from a seed and passes them to FluentTheme through rememberFluentColors.
So the same generator feeds three different component systems. Whether that is an advantage depends on your project: if you are on one of these toolkits it removes the need to hand-maintain light and dark palettes, and if you are on none of them, the core module plus MaterialKolors is the path.
The builder in the browser and the 6.0 colour specification change
MaterialKolor Builder lets you try seeds and styles in the browser and exports the theme code. While 6.0 is in beta, next.materialkolor.com runs the builder on 6.0, and API documentation lives at docs.materialkolor.com.
The 6.0 release is a specification change rather than a feature change, and that is why the README carries a note for anyone coming from 5.x, pointing at a 6.0 migration guide in the docs directory. The visible consequence of the change is the specVersion option, which defaults to SPEC_2025 and can be set to SPEC_2021 to reproduce the older colours.
The release history shows how recent this is. Version 6.0.0 shipped on 2026-10-15, preceded by 6.0.0-beta01 on 2026-10-01 and 5.0.1 on 2026-08-27. The repository is not archived and the last push was on 2026-10-19, so the work is current even though 6.0 is only days old.
The repository layout supports the multi-artifact story. Alongside the five MaterialKolor modules there is a material-color-utilities directory, a builder directory for the web tool, a samples directory with material3, unstyled, fluent, custom-theme, shared and screenshots projects, a build-logic directory and a tools directory, all wired through settings.gradle.kts and a Gradle wrapper.
Licence, and where the boundaries of this library are
The licence is MIT, with the LICENSE file at the repository root, and the project carries a CODE_OF_CONDUCT.md. The packages are published under the com.materialkolor group on Maven Central.
What this library is not is worth stating plainly, because the name suggests more than it delivers. It does not generate Figma variables, JSON design tokens or CSS. It does not produce a static palette file at build time. It does not replace a design system, and it does not decide what your app should look like; it decides what your palette is, given one colour and a set of options.
The first limitation is the one that catches teams out: the algorithm is deterministic, not authoritative. The seed drives the roles that you do not pin, so if a designer specifies an exact brand colour for a role you did not pin, the shipped colour will not be that value. Pinning roles individually works, but every unpinned role remains a function of the seed, and changing the seed changes all of them at once.
The second is the 6.0 specification shift. An app that upgrades from 5.x and does not pass SPEC_2021 will visibly change colour, and that is a design review conversation, not a changelog entry. The README does not document a compatibility matrix beyond that option, so the migration guide is the document to read.
The third is the dependency on the colour engine's version. The badges pin a specific material-color-utilities commit, and the engine's output is what determines every colour you ship, so an engine update is a visual change to your app.
Editorial conclusion
Adopt MaterialKolor when your Compose Multiplatform app needs its palette to follow one brand color on every platform, and you want the role set, the tonal palettes and the contrast handling to come from Google's own color utilities rather than from hand-picked hex values. Do not adopt it when your design system requires exact brand colors that must not be recomputed, since pinning a role still lets the seed drive everything else, or when you are stuck on version 5.x semantics, because 6.0 defaults to the SPEC_2025 color specification and older output is only produced by passing ColorSpec.SpecVersion.SPEC_2021. Verify first that your seed image workflow fits, since the material-kolor-palette adapter depends on the separate kmpalette library, that the specVersion you ship matches what your designers signed off against, and that you read docs/migration-6.0.md before upgrading.
Frequently asked questions
How do I add MaterialKolor to a Kotlin Multiplatform project?
Add the artifact for your toolkit to the commonMain source set, for example implementation("com.materialkolor:material-kolor-material3:6.0.0") inside kotlin { sourceSets { ... } }. Each adapter brings in material-kolor-core for you.
How do I keep the same colours as MaterialKolor 5.x after upgrading to 6.0?
Pass specVersion = ColorSpec.SpecVersion.SPEC_2021. Version 6.0 defaults to SPEC_2025, and the README points to a 6.0 migration guide for anyone upgrading from 5.x.
What can MaterialKolor do that the standard Material colour roles cannot?
It exposes the six tonal palettes behind the roles, so you can read any tone from 0 to 100, and onTone gives a readable content colour from the same palette aiming for WCAG AA. rememberTonalPalette can also build a palette from any colour and harmonise it with your seed.
Does MaterialKolor work with Compose Multiplatform?
Yes. The README states it runs material-color-utilities on Android, iOS, macOS, desktop JVM, JS and Wasm in Compose Multiplatform, with adapters for Material 3, Compose Unstyled and Compose Fluent, and a core module for building your own theme.
What licence is MaterialKolor released under?
MIT, with the licence text in the LICENSE file at the repository root and the packages published to Maven Central under the com.materialkolor group.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/jordond-materialkolor)Was this useful?