The Switch component provides a toggle control for binary on/off states with smooth animations and optional thumb content.
Import
import com.nomanr.lumo.ui.components.Switch
import com.nomanr.lumo.ui.components.SwitchColors
Component Signature
@Composable
fun Switch(
checked: Boolean,
onCheckedChange: ((Boolean) -> Unit)?,
modifier: Modifier = Modifier,
thumbContent: (@Composable () -> Unit)? = null,
enabled: Boolean = true,
colors: SwitchColors = SwitchDefaults.colors(),
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
)
Source: Switch.kt:53-98
Parameters
Whether the switch is currently in the on position
onCheckedChange
((Boolean) -> Unit)?
required
Callback invoked when the switch is toggled. If null, switch becomes non-interactive
modifier
Modifier
default:"Modifier"
Modifier to be applied to the switch
thumbContent
@Composable (() -> Unit)?
default:"null"
Optional icon or content to display inside the switch thumb
Whether the switch is enabled and can be toggled
colors
SwitchColors
default:"SwitchDefaults.colors()"
Colors for different switch states
Source of interactions for hover, press, and focus states
Examples
Basic Switch
var isEnabled by remember { mutableStateOf(false) }
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
Switch(
checked = isEnabled,
onCheckedChange = { isEnabled = it }
)
Text("Enable notifications")
}
Switch with Label
From preview (Switch.kt:326-385):
val value = remember { mutableStateOf(false) }
Column(modifier = Modifier.padding(16.dp)) {
// Interactive switch
Switch(
checked = value.value,
onCheckedChange = { value.value = it }
)
Spacer(modifier = Modifier.size(16.dp))
// Checked state
Switch(
checked = true,
onCheckedChange = {}
)
Spacer(modifier = Modifier.size(16.dp))
// Unchecked state
Switch(
checked = false,
onCheckedChange = {}
)
Spacer(modifier = Modifier.size(16.dp))
// Disabled checked
Switch(
checked = true,
enabled = false,
onCheckedChange = {}
)
Spacer(modifier = Modifier.size(16.dp))
// Disabled unchecked
Switch(
checked = false,
enabled = false,
onCheckedChange = {}
)
}
Switch with Icon Thumb Content
var darkMode by remember { mutableStateOf(false) }
Switch(
checked = darkMode,
onCheckedChange = { darkMode = it },
thumbContent = {
Icon(
imageVector = if (darkMode) Icons.Default.DarkMode else Icons.Default.LightMode,
contentDescription = null,
modifier = Modifier.size(12.dp)
)
}
)
Settings Toggle
data class Setting(val title: String, val description: String, var enabled: Boolean)
val settings = remember {
mutableStateListOf(
Setting("Wi-Fi", "Connect to wireless networks", true),
Setting("Bluetooth", "Connect to nearby devices", false),
Setting("Airplane Mode", "Disable wireless connections", false)
)
}
Column {
settings.forEachIndexed { index, setting ->
Row(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Column(modifier = Modifier.weight(1f)) {
Text(setting.title, style = AppTheme.typography.body1)
Text(
setting.description,
style = AppTheme.typography.caption,
color = AppTheme.colors.textSecondary
)
}
Switch(
checked = setting.enabled,
onCheckedChange = {
settings[index] = setting.copy(enabled = it)
}
)
}
if (index < settings.lastIndex) {
Divider()
}
}
}
Custom Colors
var checked by remember { mutableStateOf(false) }
Switch(
checked = checked,
onCheckedChange = { checked = it },
colors = SwitchDefaults.colors(
checkedThumbColor = Color.White,
checkedTrackColor = Color.Green,
checkedBorderColor = Color.Green,
uncheckedThumbColor = Color.Gray,
uncheckedTrackColor = Color.LightGray,
uncheckedBorderColor = Color.Gray
)
)
SwitchColors
The SwitchColors class provides extensive color customization (Switch.kt:229-282):
class SwitchColors(
private val checkedThumbColor: Color,
private val checkedTrackColor: Color,
private val checkedBorderColor: Color,
private val checkedIconColor: Color,
private val uncheckedThumbColor: Color,
private val uncheckedTrackColor: Color,
private val uncheckedBorderColor: Color,
private val uncheckedIconColor: Color,
private val disabledCheckedThumbColor: Color,
private val disabledCheckedTrackColor: Color,
private val disabledCheckedBorderColor: Color,
private val disabledCheckedIconColor: Color,
private val disabledUncheckedThumbColor: Color,
private val disabledUncheckedTrackColor: Color,
private val disabledUncheckedBorderColor: Color,
private val disabledUncheckedIconColor: Color,
)
Styling
Default Styling
From SwitchDefaults (Switch.kt:179-187):
- Track Width: 40.dp
- Track Height: 24.dp
- Thumb Size: 16.dp (checked)
- Unchecked Thumb Size: 12.dp
- Track Border Width: 2.dp
- Track Shape: RoundedCornerShape(50) - fully rounded
- Ripple Radius: 20.dp
Default Colors
From SwitchDefaults.colors() (Switch.kt:189-226):
Checked State:
- Thumb: On-primary color
- Track: Primary color
- Border: Primary color
- Icon: Primary color
Unchecked State:
- Thumb: Primary color
- Track: Background color
- Border: Primary color
- Icon: On-primary color
Disabled State:
- Uses disabled theme colors
Animations
The switch features smooth, physics-based animations (Switch.kt:285-322):
Thumb Position Animation
- Animates thumb from left (0f) to right (1f)
- Duration: 100ms
- Easing: FastOutSlowInEasing
Thumb Size Animation
- Thumb grows when pressed
- Animates from 12.dp to 16.dp when moving to checked
- Provides tactile feedback during interaction
Animation State Management
// Internal animation state (simplified)
val animationSpec = tween<Float>(
durationMillis = 100,
easing = FastOutSlowInEasing,
)
Implementation Details
Thumb Positioning
The thumb position is calculated based on:
- Current checked state
- Press state (for size changes)
- Available track width minus thumb size
- Padding within the track
From Switch.kt:140-151:
val trackWidth = SwitchWidth.toPx()
val currentThumbSize = thumbSize.toPx()
val maxThumbSize = ThumbSize.toPx()
val padding = verticalPadding.toPx()
val totalMovableDistance = trackWidth - maxThumbSize - (padding * 2)
val sizeDifference = (maxThumbSize - currentThumbSize) / 2
IntOffset(
x = (padding + sizeDifference + (totalMovableDistance * thumbPosition)).roundToInt(),
y = 0,
)
Interaction
- Uses
toggleable modifier for keyboard and accessibility support (Switch.kt:75-83)
- Ripple effect centered on thumb (Switch.kt:158-164)
- Press state affects thumb size for visual feedback
Accessibility
- Automatically assigned
Role.Switch semantic property (Switch.kt:80)
- Ripple indication provides visual feedback (Switch.kt:158-164)
- Disabled state clearly indicated with different colors
- Thumb content inherits correct icon color via
LocalContentColor (Switch.kt:169-173)
- Clear on/off visual distinction
Advanced Usage
data class FormData(
var receiveEmails: Boolean = true,
var receiveSMS: Boolean = false,
var pushNotifications: Boolean = true
)
val formData = remember { mutableStateOf(FormData()) }
Column {
SwitchRow(
label = "Email Notifications",
checked = formData.value.receiveEmails,
onCheckedChange = {
formData.value = formData.value.copy(receiveEmails = it)
}
)
SwitchRow(
label = "SMS Notifications",
checked = formData.value.receiveSMS,
onCheckedChange = {
formData.value = formData.value.copy(receiveSMS = it)
}
)
SwitchRow(
label = "Push Notifications",
checked = formData.value.pushNotifications,
onCheckedChange = {
formData.value = formData.value.copy(pushNotifications = it)
}
)
}
@Composable
fun SwitchRow(
label: String,
checked: Boolean,
onCheckedChange: (Boolean) -> Unit,
modifier: Modifier = Modifier
) {
Row(
modifier = modifier
.fillMaxWidth()
.padding(16.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Text(label)
Switch(checked = checked, onCheckedChange = onCheckedChange)
}
}
Source Reference
Full implementation: com.nomanr.lumo.ui.components.Switch.kt