Documentation
Build your catalog
Parameters & controls
Explore runtime controls for Compose scene parameters, including strings, enums, sealed objects, custom options, and nullable values.
Parameters And Controls
Scene parameters are declared inside the scene body with delegated properties:
internal val buttonPlayground by scene(group = "Buttons") { contentPadding ->
val enabled: Boolean by param(true)
var title: String by param("Continue")
Box(Modifier.padding(contentPadding)) {
AppButton(
text = title,
enabled = enabled,
onClick = {
title = if (title == "Continue") "Saved" else "Continue"
},
)
}
}
param(...) delegates can be declared as var. This lets the scene update a parameter from its own code while Composium still exposes the same value in the settings panel.
Parameter names must be unique
Parameter names must be unique within a scene, including parameters declared by helper composables. By default, the name matches the delegated property; pass name to set a different label:
val title: String by param("Hello", name = "Title")
val subtitle: String by param("World", name = "Subtitle")
Duplicate names throw IllegalStateException identifying the conflict. This also applies when changing name dynamically. Different scenes can use the same parameter names.
How Composium renders parameter controls
| Parameter kind | UI control | Notes |
|---|---|---|
Boolean |
Switch | Automatic |
String |
Text field | Automatic |
enum |
Option chips | Values inferred automatically |
| Sealed object hierarchy | Option chips | Values inferred automatically from object instances |
| Nullable supported parameter | Checkbox + underlying control | Checkbox toggles null-state; do not add null to options manually |
| Any other type with explicit options | Option chips | Use listOf(...).toParamOptions() or explicit named values |
Important detail: you can use any type as a parameter value, but interactive selection for custom and numeric types requires explicit options unless Composium can infer them automatically.
For referential or non-static types such as Painter, prefer explicit named options.
Automatic options for Boolean, enum, and sealed object hierarchies
enum class ButtonVariant {
Primary,
Secondary,
Danger,
}
sealed interface ButtonSize {
object Small : ButtonSize
object Medium : ButtonSize
object Large : ButtonSize
}
internal val buttonPlayground by scene(group = "Buttons") { contentPadding ->
val enabled: Boolean by param(true)
val variant: ButtonVariant by param(ButtonVariant.Primary)
val size: ButtonSize by param(ButtonSize.Medium)
Box(Modifier.padding(contentPadding)) {
AppButton(
enabled = enabled,
variant = variant,
size = size,
)
}
}
What happens in the UI:
enabledis shown as a switch;variantis shown as chips with enum entries;sizeis shown as chips with inferred sealed object options.
Explicit options for numbers and custom values
For types like Int, Long, Float, Double, or custom objects, define the allowed values explicitly.
Inside scene {} you can convert raw values to List<ParamOption<T>> with toParamOptions(). If you want full control over labels, pass explicit named values instead.
If the parameter type is nullable, pass only the non-null choices. Composium handles the null state through the checkbox automatically.
internal val spacingPlayground by scene(group = "Spacing") { contentPadding ->
val elevation: Int by param(
default = 0 named "None",
options = listOf(
0 named "None",
2 named "2dp",
8 named "8dp",
16 named "16dp",
),
)
val alpha: Float by param(
default = 1f,
options = listOf(0.25f, 0.5f, 0.75f, 1f).toParamOptions(),
)
Box(Modifier.padding(contentPadding)) {
ExampleCard(
elevation = elevation,
alpha = alpha,
)
}
}
Custom names
You can override how options are shown in the controls UI.
default can also be passed as a named option with infix syntax:
val mode by param(
default = DisplayMode.Grid named "Grid",
options = listOf(
DisplayMode.Grid named "Grid",
DisplayMode.List named "List",
),
)
Using a name mapper:
val role by param(
default = UserRole.Member,
options = listOf(
UserRole.Admin,
UserRole.Member,
UserRole.Guest,
).toParamOptions { role ->
when (role) {
UserRole.Admin -> "Administrator"
UserRole.Member -> "Member"
UserRole.Guest -> "Guest"
}
},
)
Using explicit named values:
val alignment by param(
default = AlignmentMode.Center,
options = listOf(
AlignmentMode.Start named "Start",
AlignmentMode.Center named "Center",
AlignmentMode.End named "End",
),
)
Using a named default without explicit options:
val leadingIcon: Painter? by param(
painterResource(R.drawable.solid_attention) named "Attention",
)
For referential values such as Painter, names act as the stable identity for explicit option chips. In these cases, name every option and name the default too when it is declared independently from the option list.
If explicit option names collide inside the same parameter, Composium will append numeric suffixes automatically until every name becomes unique.
Nullable parameters
Nullable parameters get an extra checkbox that controls whether the value is currently null.
internal val cardPlayground by scene(group = "Cards") { contentPadding ->
val maxLines: Int? by param(
default = null,
options = listOf(
1 named "1 line",
2 named "2 lines",
3 named "3 lines",
),
)
val leadingIcon: Painter? by param(
painterResource(R.drawable.solid_attention) named "Attention",
)
val subtitle: String? by param(null)
Box(Modifier.padding(contentPadding)) {
ExampleCard(
maxLines = maxLines,
leadingIcon = leadingIcon,
subtitle = subtitle,
)
}
}
What happens in the UI:
- nullable parameters get a checkbox in the control header;
- unchecked means the parameter is currently
null; - checked restores the value and shows its regular control.
For nullable parameters with explicit options, do not include null in the list yourself. Pass only non-null values and let Composium manage the null-state toggle.
If you declare a nullable parameter with param(options = ...) and do not provide an explicit default, Composium uses the first option from the list as the initial value (including null if it is first).
Reordering auto-inferred options
For automatically inferred sealed options, you can still override their display order:
By default, auto-inferred sealed options are sorted by their generated names using natural ordering (for example, r2, r10, r100).
val size: ButtonSize by param(ButtonSize.Medium) { inferred ->
inferred.reversed()
}