Skip to main content
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

checked
Boolean
required
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
enabled
Boolean
default:"true"
Whether the switch is enabled and can be toggled
colors
SwitchColors
default:"SwitchDefaults.colors()"
Colors for different switch states
interactionSource
MutableInteractionSource
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:
  1. Current checked state
  2. Press state (for size changes)
  3. Available track width minus thumb size
  4. 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

Form Integration

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

Build docs developers (and LLMs) love