Skip to content

Common Patterns

A complete 360° dial starting at 12 o’clock. The thumb can travel all the way around.

var degree by remember { mutableFloatStateOf(0f) }
Dial(
degree = degree,
onDegreeChange = { degree = it },
modifier = Modifier.size(200.dp),
startDegrees = 0f,
sweepDegrees = 360f,
)

A half-arc stretched horizontally across the top. Use DialLayout.height() so the radius is based on the component height rather than width, keeping the arc flush with the edges.

var degree by remember { mutableFloatStateOf(90f) }
Dial(
degree = degree,
onDegreeChange = { degree = it },
modifier = Modifier.size(200.dp, 100.dp),
startDegrees = 270f,
sweepDegrees = 180f,
layout = DialLayout.height(),
)

For a true bottom-anchored gauge (the flat edge along the bottom), move the center down and let the radius fill the full height — see DialLayout:

layout = DialLayout(
radiusMode = RadiusMode.HEIGHT,
radiusFraction = 1f,
center = Offset(0.5f, 1f), // center on the bottom edge
)

Discrete stops like a camera mode dial. Combine interval with animateFloatAsState so the thumb visually snaps to each position.

var degree by remember { mutableFloatStateOf(90f) }
val animatedDegree by animateFloatAsState(degree)
Dial(
degree = animatedDegree,
onDegreeChange = { degree = it },
modifier = Modifier.size(200.dp),
startDegrees = -90f,
sweepDegrees = 220f,
interval = 20f,
)

When sweepDegrees exceeds 360°, the default track stacks completed rotations as fading outer rings. Useful for timers and fine-grained controls.

val sweepDegrees = 360f * 4
var degree by remember { mutableFloatStateOf(sweepDegrees) }
Dial(
degree = degree,
onDegreeChange = { degree = it },
modifier = Modifier.size(300.dp),
sweepDegrees = sweepDegrees,
interval = 6f,
)

Use State-based API to drive the dial from code with state.animateTo().

val state = rememberDialState()
val scope = rememberCoroutineScope()
Dial(state = state, sweepDegrees = 360f)
Button(onClick = { scope.launch { state.animateTo(180f) } }) {
Text("Go to halfway")
}