Dial Basics
The Dial is a circular draggable control. Users spin it by dragging along its circumference, and you get back a degree value you can use however you like — to control volume, pick a temperature, set a timer, or anything in between.
Your first Dial
Section titled “Your first Dial”var degree by remember { mutableFloatStateOf(0f) }
Dial( degree = degree, onDegreeChange = { degree = it }, modifier = Modifier.size(200.dp),)Dial is a controlled component — you own the state. Pass the current degree in, receive updates via onDegreeChange, and store the new value yourself.
Shaping the arc
Section titled “Shaping the arc”Two parameters control where the dial sits on screen and how far it sweeps:
startDegrees— the screen angle wheredegree = 0. Think of a clock:0fis 12 o’clock,90fis 3 o’clock,180fis 6 o’clock,270fis 9 o’clock.sweepDegrees— how many degrees the user can rotate from the start to the end. Defaults to360f. Can exceed 360 for multi-rotation dials.
Dial( degree = degree, onDegreeChange = { degree = it }, startDegrees = 225f, // starts at ~7 o'clock sweepDegrees = 270f, // sweeps ¾ of the way around)Snapping
Section titled “Snapping”By default the rotation is continuous. Use interval or steps to add discrete snap points:
interval = 15f // snaps every 15°steps = 12 // 12 evenly spaced stops across the sweepsteps is handy when you know how many positions you need. interval is handy when you care about the spacing between them. Setting both? steps wins.
Reading the value
Section titled “Reading the value”degree is the raw angle from 0 to sweepDegrees. Two derived readings are usually more convenient:
value— normalized0f..1facross the sweepmappedValue—valuerescaled into avalueRangeyou choose (temperature, volume, percent…)
Both live on DialState. The simplest way to read them outside the dial is the state-based API, where you hold the state yourself:
val state = rememberDialState()
Dial( state = state, sweepDegrees = 270f, valueRange = 0f..100f,)
Text("${state.mappedValue.toInt()}°C")Inside a custom thumb or track, the same state is handed to you — so you can render the value right on the dial. See Custom Thumb & Track.
Counterclockwise
Section titled “Counterclockwise”Set clockwise = false and the thumb moves the other way. Everything else — degree, value, startDegrees — behaves the same.
Sizing and position
Section titled “Sizing and position”By default the dial is centered in its bounds and its radius is half the width. Pass a layout to change that — base the radius on height, shrink it to leave room for labels, or move the center (handy for gauges anchored to the bottom edge):
Dial( degree = degree, onDegreeChange = { degree = it }, modifier = Modifier.size(200.dp, 110.dp), startDegrees = 270f, sweepDegrees = 180f, layout = DialLayout.height(), // radius from height instead of width)See DialLayout for radiusFraction and custom center positioning.
Disabling the dial
Section titled “Disabling the dial”Set enabled = false to make the dial non-interactive. It still renders, but ignores drag input and hides the pointer cursor.
What’s next
Section titled “What’s next”- Common Patterns — full circle, semi-circle, stepped, multi-rotation, and more
- Default Dial Colors — customizing colors with
DialColors - Custom Thumb & Track — full visual control with composable slots
- State-based API — manage state externally for programmatic animation
- Overshoot — the rubber-band effect at the limits
- Responding to Input — react to drag, hover, and release events