Documentation

Start here

Getting started

Install Composium from Maven Central, configure KSP, and build your first Jetpack Compose scene catalog.

Requirements

  • Android minSdk 24
  • Consumer project compileSdk 36
  • JVM target 11
  • Kotlin 2.3.21 or newer
  • mavenCentral() and google() in your consumer project

If you use KSP, choose a KSP2 plugin version compatible with your Kotlin and AGP versions. The baseline configuration uses Kotlin 2.3.21, KSP 2.3.9, AGP 8.10.1, and Gradle 8.11.1.

Installation

Set your app or host module to compileSdk 36 before adding Composium.

Add repositories:

kotlin
repositories {
    google()
    mavenCentral()
}
kotlin
plugins {
    id("com.google.devtools.ksp") version "<ksp-version>"
}

dependencies {
    implementation("io.github.oleginvoke:composium:1.3.0-alpha03")
    ksp("io.github.oleginvoke:composium-processor:1.3.0-alpha03")
}

Use this mode when you want automatic scene collection.

In a multi-module project, keep all scene declarations and the Composium KSP processor in a single showcase module, such as :sample or :catalog. That module depends on the modules containing the UI components and declares scenes for those components. It can run as a standalone sample app or expose a public composable entry point for a host app to display the showcase.

Automatic discovery collects scenes declared in that showcase module; it does not collect annotated scenes from compiled dependencies. Do not configure the Composium processor in multiple scene-containing modules included in the same app: each generates the same registry class, causing a duplicate-class build error. The runtime dependency itself can be used in multiple modules; this restriction applies to scene discovery and registry generation.

Optional: without KSP

kotlin
dependencies {
    implementation("io.github.oleginvoke:composium:1.3.0-alpha03")
}

Use this mode when you do not want to add KSP to the consumer project. In this case:

  • do not add the processor;
  • do not use @ComposiumScene or @ComposiumSceneCatalog;
  • register scenes manually through Composium.registerAll(...).

Quick Start With KSP

KSP is the primary integration path.

There are two discovery styles:

  • annotate individual scene properties with @ComposiumScene;
  • or annotate an object with @ComposiumSceneCatalog.

You do not need to use both at the same time. Pick the style that matches how you want to organize scenes in your project.

Option 1: annotate individual scene properties

Use this style when you want flat, explicit scene declarations and prefer to mark each scene directly.

kotlin
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import oleginvoke.com.composium.ComposiumScene
import oleginvoke.com.composium.ComposiumScreen
import oleginvoke.com.composium.scene

enum class ButtonVariant {
    Primary,
    Secondary,
    Danger,
}

sealed interface ButtonSize {
    object Small : ButtonSize
    object Medium : ButtonSize
    object Large : ButtonSize
}

@ComposiumScene
internal val primaryButton by scene(
    group = "Buttons/Primary",
    name = "Filled",
) { contentPadding ->
    val enabled: Boolean by param(true)
    val text: String by param("Continue")
    val variant: ButtonVariant by param(ButtonVariant.Primary)
    val size: ButtonSize by param(ButtonSize.Medium)

    Box(Modifier.padding(contentPadding)) {
        AppButton(
            text = text,
            enabled = enabled,
            variant = variant,
            size = size,
        )
    }
}
@Composable
fun DebugCatalog() {
    ComposiumScreen()
}

Option 2: annotate a scene catalog object

Use this style when you want to keep several related scenes together inside one object and let KSP collect all non-private Scene properties from it.

kotlin
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import oleginvoke.com.composium.ComposiumSceneCatalog
import oleginvoke.com.composium.ComposiumScreen
import oleginvoke.com.composium.scene

@ComposiumSceneCatalog
internal object FormScenes {
    val loginDefault by scene(group = "Forms/Auth", name = "Login / default") { contentPadding ->
        Box(Modifier.padding(contentPadding)) {
            LoginForm()
        }
    }

    val loginLoading by scene(group = "Forms/Auth", name = "Login / loading") { contentPadding ->
        Box(Modifier.padding(contentPadding)) {
            LoginForm(isLoading = true)
        }
    }
}

@Composable
fun DebugCatalog() {
    ComposiumScreen()
}

What KSP collects:

  • every property annotated with @ComposiumScene;
  • every non-private Scene property declared inside an object annotated with @ComposiumSceneCatalog.

These are two independent discovery styles, not a required pair of annotations on the same scene setup.

Quick Start Without KSP

Manual mode uses the same scene {} API, but skips both KSP and annotations.

kotlin
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Modifier
import oleginvoke.com.composium.Composium
import oleginvoke.com.composium.ComposiumScreen
import oleginvoke.com.composium.scene

internal val primaryButton by scene(group = "Buttons/Primary", name = "Filled") { contentPadding ->
    Box(Modifier.padding(contentPadding)) {
        AppButton(text = "Continue")
    }
}

internal object FormScenes {
    val loginDefault by scene(group = "Forms/Auth", name = "Login / default") { contentPadding ->
        Box(Modifier.padding(contentPadding)) {
            LoginForm()
        }
    }
}

@Composable
fun DebugCatalog() {
    LaunchedEffect(Unit) {
        Composium.registerAll(
            primaryButton,
            FormScenes.loginDefault,
        )
    }

    ComposiumScreen()
}

Notes:

  • manual registration identifies scenes by their group and name;
  • scenes still use the same runtime API as KSP mode;
  • annotations are not needed in manual mode.

For both automatic and manual registration, scene names must be unique within a group. Registering the same Scene instance again is safe. If a different scene uses the same group and name, the first scene is kept and Composium logs a warning with the Composium tag.

Repeated reads of a property declared with val MyScene by scene { ... } return the same Scene instance. Reading the property does not render its content.

When constructing Scene(...) directly, keep and reuse the instance for repeated registration.

Documentation follows the project README.Improve this page