Compose Unstyled

Toggle Switch

A switch component with an animated thumb slot.

View Markdown
ToggleSwitchDemo.kt
import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.tween
import androidx.compose.foundation.LocalIndication
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.composeunstyled.Text
import com.composeunstyled.Thumb
import com.composeunstyled.Track
import com.composeunstyled.UnstyledSwitch
import com.composeunstyled.demo.borderToken
import com.composeunstyled.demo.colors
import com.composeunstyled.demo.contentToken
import com.composeunstyled.demo.inputBackgroundToken
import com.composeunstyled.demo.surfaceToken
import com.composeunstyled.theme.Theme

@Preview
@Composable
fun ToggleSwitchDemo() {
  var toggled by remember { mutableStateOf(true) }
  val backgroundColor by animateColorAsState(
    if (toggled) Theme[colors][contentToken] else Theme[colors][inputBackgroundToken],
  )
  val pillShape = RoundedCornerShape(100)

  Box(
    modifier = Modifier.fillMaxSize(),
    contentAlignment = Alignment.Center,
  ) {
    UnstyledSwitch(
      checked = toggled,
      onCheckedChange = { toggled = it },
      modifier = Modifier
        .width(300.dp)
        .clip(RectangleShape),
      indication = LocalIndication.current,
    ) {
      Row(
        modifier = Modifier
          .fillMaxWidth()
          .padding(8.dp),
        horizontalArrangement = Arrangement.SpaceBetween,
        verticalAlignment = Alignment.CenterVertically,
      ) {
        Text("Airplane Mode", fontSize = 18.sp)
        Track(
          modifier = Modifier
            .width(58.dp)
            .height(32.dp)
            .clip(pillShape)
            .background(backgroundColor, pillShape)
            .border(1.dp, Theme[colors][borderToken], pillShape),
        ) {
          Thumb(
            animationSpec = tween(),
            modifier = Modifier
              .padding(4.dp)
              .clip(CircleShape)
              .background(Theme[colors][surfaceToken])
              .border(1.dp, Theme[colors][borderToken], CircleShape)
              .size(24.dp),
          )
        }
      }
    }
  }
}

Installation

implementation("com.composables:composeunstyled-toggle-switch:2.9.2")

Anatomy

UnstyledSwitch(
  checked = checked,
  onCheckedChange = onCheckedChange,
) {
  SwitchThumb {
  }
}

Concepts

  • UnstyledSwitch represents the interactive switch.
  • SwitchThumb places thumb content at the start or end of the switch layout.

Accessibility

Use the onCheckedChange parameter to make the switch interactive. Set it to null only when an accessible parent component owns the toggle interaction.

Code Examples

Toggling a switch

Use the checked and onCheckedChange parameters to control switch state:

var checked by remember { mutableStateOf(false) }

UnstyledSwitch(
  checked = checked,
  onCheckedChange = { checked = it },
) {
  SwitchThumb {
    BasicText(if (checked) "On" else "Off")
  }
}

Animating the switch thumb

Use the animationSpec parameter on SwitchThumb to change the thumb animation:

UnstyledSwitch(
  checked = checked,
  onCheckedChange = { checked = it },
) {
  SwitchThumb(animationSpec = tween(durationMillis = 200)) {
    BasicText(if (checked) "On" else "Off")
  }
}

Moving toggle behavior to a parent

Use the onCheckedChange parameter with null when a parent toggleable surface owns the interaction. This is useful when the switch is only the visual control inside a larger row.

Row(
  modifier = Modifier.toggleable(
    value = checked,
    role = Role.Switch,
    onValueChange = { checked = it },
  ),
) {
  BasicText("Notifications")

  UnstyledSwitch(
    checked = checked,
    onCheckedChange = null,
  ) {
    SwitchThumb {
      BasicText(if (checked) "On" else "Off")
    }
  }
}

API Reference

UnstyledSwitch

Parameter Type Description
checked Boolean Whether the switch is on or off.
onCheckedChange ((Boolean) -> Unit)? Callback when the switch changes state. Pass null when another control owns the interaction.
modifier Modifier Modifier to be applied to the switch.
enabled Boolean Whether the switch is enabled.
interactionSource MutableInteractionSource? Interaction source for press, focus, and drag interactions.
indication Indication? Indication used for the switch interaction.
accessibilityLabel String?
content SwitchScope.() -> Unit Content drawn inside the switch scope.

SwitchScope

Parameter Type Description
checked Boolean Whether the switch is on or off.
enabled Boolean Whether the switch is enabled.
interactionSource MutableInteractionSource Interaction source for press, focus, and drag interactions.

SwitchScope.SwitchThumb

Parameter Type Description
modifier Modifier Modifier to apply to the thumb container.
animationSpec FiniteAnimationSpec<Dp> Animation used when the thumb moves between states.
content () -> Unit Thumb content.