Skip to main content
The Compose output mirrors the Swift output closely: per-mode token files with statically resolved colors, a Material 3 ColorScheme bridge for interop with existing theming, and a Kotlin port of the color kernel for anything continuous.

What gets generated

Each brand emits a directory under generated/brands/<brand>/compose/: As on iOS, there is no system.dimmed.gen.kt — a mid-track position at scheme: 0.65 has no endpoint to bake and needs continuous sampling through the kernel.

The token files

Example output for a fictional acme brand — generated/brands/acme/compose/system.light.gen.kt:
Colors are Display P3, and each intent is a surface/text/border triple — the same three fields as Swift. The roles are surface, surfaceElevated, brand, neutral, danger, warning, and success, plus any custom intents the brand declares. There are no spacing or type-size constants in the token output.
The generated Compose files declare no package. They go straight from the generated header to the imports, so there is no package line to edit and no import statement to write in consuming code — the declarations are top-level in whatever source set you add them to.All four per-mode files declare the same top-level names, so adding two of them to one source set fails to compile on duplicate SubstrateSystemTokens and SubstrateSystemTokenSet declarations. Include exactly one per build variant — for example, via variant-specific source sets in Gradle.

The Material ColorScheme bridge

If your app already themes through Material 3, material-scheme.gen.kt is the integration point. It maps Substrate’s solved intents onto Material’s role names and exposes a ready ColorScheme for each of light and dark:
The object is named per brand, so an acme brand produces AcmeMaterialScheme. Unlike the per-mode token files, this one carries both schemes in a single file, so runtime light/dark selection is straightforward:
Material’s role vocabulary is narrower than Substrate’s. The bridge covers the Material roles; intents outside that set — custom brand intents, beta, info — stay available through SubstrateSystemTokens or the kernel.

The kernel port

packages/kernel-kotlin mirrors the Swift package, in the substrate.kernel package: Apca.kt, Oklch.kt, Cvd.kt, Warmth.kt, Track.kt, Pipeline.kt, Preferences.kt, Surface.kt, ColorResolve.kt, StyleDescriptor.kt, and BrandData.kt. Unlike the generated token files, the kernel does declare a package, so you import from it normally:
Reach for the kernel when static files can’t express what you need: a continuous scheme position such as dimmed, a user-chosen contrastFactor, CVD compensation, or warmth. A conformance harness holds the port to the TypeScript kernel’s computed values, so Android, iOS, and web agree on the same inputs.

Component descriptors

Component styling ships as data, interpreted by the kernel. The per-brand index.gen.kt aggregates the brand data and each component’s resolved descriptor — the brand override where one exists, otherwise the global descriptor:
Every generated file carries a @generated-substrate header and is a build artifact. Never edit one — change the brand YAML and regenerate with npm run generate:tokens:compose from a Substrate checkout.

iOS / Swift

The SwiftPM mirror of this output and the SubstrateKernel package.

Modes Overview

The preference vector behind the four baked modes, and why dimmed isn’t one of them.