Compose Unstyled 2.0 is out! Check the official announcement blog ->

Toggle Switch

A switch component with an animated thumb slot.

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.Row
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.selection.selectable
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.BasicText
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.Color
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.composeunstyled.SwitchThumb
import com.composeunstyled.UnstyledSwitch

@Composable
fun ToggleSwitchDemo() {
  var toggled by remember { mutableStateOf(true) }

  Row(
    Modifier
      .width(300.dp)
      .clip(RoundedCornerShape(10.dp))
      .selectable(
        selected = toggled,
        onClick = { toggled = toggled.not() },
        interactionSource = null,
        role = Role.Switch,
      ).padding(8.dp),
    horizontalArrangement = Arrangement.SpaceBetween,
    verticalAlignment = Alignment.CenterVertically,
  ) {
    BasicText("Airplane Mode", style = TextStyle(fontSize = 18.sp))
    val animatedColor by animateColorAsState(
      if (toggled) Color.Black else Color(0xFFE0E0E0),
    )
    UnstyledSwitch(
      checked = toggled,
      onCheckedChange = null,
      modifier = Modifier
        .width(58.dp)
        .height(32.dp)
        .clip(RoundedCornerShape(100))
        .background(animatedColor, RoundedCornerShape(100))
        .border(1.dp, Color(0xFFCACACA), RoundedCornerShape(100)),
      indication = LocalIndication.current,
    ) {
      SwitchThumb(
        animationSpec = tween(),
        modifier = Modifier
          .padding(4.dp)
          .clip(CircleShape)
          .background(Color(0xFFF8FAFC))
          .border(1.dp, Color(0xFFCACACA), CircleShape)
          .size(24.dp),
      )
    }
  }
}

Installation

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

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.
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.