Bookmark this page. Use Ctrl+F (or Cmd+F on Mac) to find what you need. This cheat sheet covers Jetpack Compose components, modifiers, state, navigation, and common patterns.
Last updated: April 2026
Core Composables
| Composable | Usage |
|---|---|
Text("Hello") | Display text |
Text("Bold", fontWeight = FontWeight.Bold) | Styled text |
Button(onClick = { }) { Text("Click") } | Clickable button |
OutlinedButton(onClick = { }) { Text("Outlined") } | Outlined variant |
TextButton(onClick = { }) { Text("Text") } | Text-only button |
IconButton(onClick = { }) { Icon(...) } | Icon button |
TextField(value, onValueChange = { }) | Text input field |
OutlinedTextField(value, onValueChange = { }) | Outlined text input |
Image(painter, contentDescription) | Display an image |
Icon(Icons.Default.Home, contentDescription) | Material icon |
Checkbox(checked, onCheckedChange) | Checkbox |
Switch(checked, onCheckedChange) | Toggle switch |
RadioButton(selected, onClick) | Radio button |
Slider(value, onValueChange) | Slider |
CircularProgressIndicator() | Loading spinner |
LinearProgressIndicator(progress) | Progress bar |
HorizontalDivider() | Horizontal divider line (replaces deprecated Divider()) |
Spacer(modifier = Modifier.height(16.dp)) | Empty space |
Layouts
// Column — vertical stack
Column {
Text("First")
Text("Second")
}
// Row — horizontal stack
Row {
Text("Left")
Spacer(Modifier.weight(1f))
Text("Right")
}
// Box — stack on top of each other
Box {
Image(...) // background
Text("Overlay") // on top
}
// LazyColumn — scrollable vertical list (RecyclerView replacement)
LazyColumn {
items(list) { item ->
Text(item.name)
}
}
// LazyRow — scrollable horizontal list
LazyRow {
items(list) { item ->
Card { Text(item.name) }
}
}
// LazyVerticalGrid — grid layout
LazyVerticalGrid(columns = GridCells.Fixed(2)) {
items(list) { item ->
Card { Text(item.name) }
}
}
Common Modifiers
Modifiers change the appearance and behavior of composables. Order matters — modifiers are applied top to bottom.
| Modifier | Effect |
|---|---|
.padding(16.dp) | Add padding inside |
.padding(horizontal = 16.dp, vertical = 8.dp) | Directional padding |
.fillMaxWidth() | Fill parent width |
.fillMaxSize() | Fill parent width + height |
.size(48.dp) | Fixed width and height |
.width(200.dp) / .height(100.dp) | Fixed one dimension |
.weight(1f) | Fill remaining space (in Row/Column) |
.background(Color.Red) | Background color |
.background(Color.Red, RoundedCornerShape(8.dp)) | Rounded background |
.clip(RoundedCornerShape(12.dp)) | Clip to shape |
.clip(CircleShape) | Clip to circle |
.border(1.dp, Color.Gray, RoundedCornerShape(8.dp)) | Add border |
.clickable { } | Make clickable |
.shadow(4.dp, RoundedCornerShape(8.dp)) | Drop shadow |
.alpha(0.5f) | Transparency |
.offset(x = 10.dp, y = 5.dp) | Move position |
.align(Alignment.CenterHorizontally) | Align in Column |
.verticalScroll(rememberScrollState()) | Make scrollable |
Modifier Order Matters
// Padding THEN background — padding is outside the color
Box(Modifier.padding(16.dp).background(Color.Red))
// Background THEN padding — padding is inside the color
Box(Modifier.background(Color.Red).padding(16.dp))
State
// remember — survives recomposition
var count by remember { mutableStateOf(0) }
// rememberSaveable — survives configuration changes (rotation)
var name by rememberSaveable { mutableStateOf("") }
// derivedStateOf — computed state that only updates when dependencies change
val isValid by remember {
derivedStateOf { name.isNotBlank() && name.length >= 3 }
}
// Collecting Flow as State
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
State Hoisting Pattern
// Stateless composable — state is "hoisted" to the caller
@Composable
fun Counter(count: Int, onIncrement: () -> Unit) {
Button(onClick = onIncrement) {
Text("Count: $count")
}
}
// Stateful parent
@Composable
fun CounterScreen() {
var count by remember { mutableStateOf(0) }
Counter(count = count, onIncrement = { count++ })
}
Navigation (Type-Safe)
Type-safe navigation (recommended since 2024) uses Kotlin Serialization instead of string routes:
// Define routes as data classes
@Serializable data object Home
@Serializable data class Detail(val id: String)
// Setup
val navController = rememberNavController()
NavHost(navController, startDestination = Home) {
composable<Home> { HomeScreen(navController) }
composable<Detail> { backStackEntry ->
val detail = backStackEntry.toRoute<Detail>()
DetailScreen(detail.id)
}
}
// Navigate
navController.navigate(Detail(id = "42"))
// Navigate and clear back stack
navController.navigate(Home) {
popUpTo<Home> { inclusive = true }
}
// Go back
navController.popBackStack()
Material 3 Components
// Scaffold — app structure with top bar, bottom bar, FAB
Scaffold(
topBar = { TopAppBar(title = { Text("My App") }) },
bottomBar = { NavigationBar { /* items */ } },
floatingActionButton = {
FloatingActionButton(onClick = { }) { Icon(Icons.Default.Add, null) }
}
) { padding ->
// Content goes here
Column(Modifier.padding(padding)) { }
}
// Card
Card(
modifier = Modifier.fillMaxWidth().padding(8.dp),
elevation = CardDefaults.cardElevation(defaultElevation = 4.dp)
) {
Text("Card content", Modifier.padding(16.dp))
}
// AlertDialog
AlertDialog(
onDismissRequest = { showDialog = false },
title = { Text("Confirm") },
text = { Text("Are you sure?") },
confirmButton = { TextButton(onClick = { }) { Text("Yes") } },
dismissButton = { TextButton(onClick = { }) { Text("No") } }
)
// BottomSheet
ModalBottomSheet(onDismissRequest = { }) {
Column(Modifier.padding(16.dp)) {
Text("Bottom sheet content")
}
}
Theming
// Access current theme values anywhere
MaterialTheme.colorScheme.primary
MaterialTheme.colorScheme.surface
MaterialTheme.colorScheme.onSurface
MaterialTheme.typography.headlineMedium
MaterialTheme.typography.bodyLarge
MaterialTheme.shapes.medium
Side Effects
| Effect | When to use |
|---|---|
LaunchedEffect(key) { } | Run suspend function when key changes |
DisposableEffect(key) { onDispose { } } | Setup + cleanup (like listeners) |
rememberCoroutineScope() | Get scope for launching coroutines from callbacks |
SideEffect { } | Run after every successful recomposition |
rememberUpdatedState(value) | Capture latest value in a long-running effect |
// Load data when screen appears
LaunchedEffect(Unit) {
viewModel.loadData()
}
// Cleanup a listener
DisposableEffect(lifecycleOwner) {
val observer = LifecycleEventObserver { _, event -> }
lifecycleOwner.lifecycle.addObserver(observer)
onDispose { lifecycleOwner.lifecycle.removeObserver(observer) }
}
// Launch coroutine from a click handler
val scope = rememberCoroutineScope()
Button(onClick = { scope.launch { doWork() } }) {
Text("Start")
}
Animations
// AnimatedVisibility — show/hide with animation
AnimatedVisibility(visible = isVisible) {
Text("I fade in and out")
}
// animateContentSize — smooth size changes
Column(Modifier.animateContentSize()) {
Text("Title")
if (expanded) {
Text("Extra content that animates in")
}
}
// Animate a value
val alpha by animateFloatAsState(if (visible) 1f else 0f)
Box(Modifier.alpha(alpha)) { Text("Fading") }
// Crossfade between composables
Crossfade(targetState = currentTab) { tab ->
when (tab) {
Tab.Home -> HomeScreen()
Tab.Profile -> ProfileScreen()
}
}
Flow Layouts
// FlowRow — wraps items to next line when full
FlowRow(
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
tags.forEach { tag ->
AssistChip(onClick = { }, label = { Text(tag) })
}
}
// FlowColumn — vertical wrapping
FlowColumn(
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
items.forEach { Text(it) }
}
Common Patterns
Loading / Error / Success
when (val state = uiState) {
is UiState.Loading -> CircularProgressIndicator()
is UiState.Error -> Text("Error: ${state.message}")
is UiState.Success -> LazyColumn {
items(state.data) { item -> ItemCard(item) }
}
}
Search Field with Debounce
var query by remember { mutableStateOf("") }
LaunchedEffect(query) {
delay(300) // debounce 300ms
viewModel.search(query)
}
OutlinedTextField(value = query, onValueChange = { query = it })
Pull-to-Refresh
// Material 3 pull-to-refresh
val pullState = rememberPullToRefreshState()
PullToRefreshBox(
isRefreshing = isRefreshing,
onRefresh = { viewModel.refresh() },
state = pullState
) {
LazyColumn { /* content */ }
}
Note: Navigation3 (
NavDisplay, scenes) became stable in March 2026 as a Compose-first alternative toNavController/NavHost. For new projects, consider evaluating Navigation3 alongside the type-safe Navigation Compose approach shown above.
Common Mistakes
Forgetting
remember— Withoutremember, state resets on every recomposition.var count = mutableStateOf(0)resets to 0 every time the composable re-executes. Always usevar count by remember { mutableStateOf(0) }.Wrong modifier order —
Modifier.padding(16.dp).background(Color.Red)gives padding outside the red area.Modifier.background(Color.Red).padding(16.dp)gives padding inside. Think of modifiers as wrapping layers from outside to inside.Recomposition traps — Avoid creating new objects inside composables without
remember. For example,val list = listOf(1, 2, 3)creates a new list on every recomposition. Useval list = remember { listOf(1, 2, 3) }for stable references.
Related Resources
- Jetpack Compose Tutorial Series (25 parts) — learn Compose from scratch
- Kotlin Cheat Sheet — Kotlin syntax quick reference
- KMP Tutorial Series — share Compose across Android, iOS, Desktop
- All Cheat Sheets