Composables are the building blocks of Compose UI. Here’s a reference for the most commonly used composables.
Text
// Basic
Text("Hello, World!")
// Styled
Text(
text = "Hello, World!",
style = MaterialTheme.typography.headlineMedium,
color = MaterialTheme.colorScheme.primary,
)
// Rich text with AnnotatedString
Text(
buildAnnotatedString {
withStyle(SpanStyle(fontWeight = FontWeight.Bold)) {
append("Hello")
}
append(", ")
withStyle(SpanStyle(color = MaterialTheme.colorScheme.primary)) {
append("World")
}
append("!")
}
)
// Clickable text
ClickableText(
text = AnnotatedString("Terms and Conditions"),
onClick = { offset -> /* handle click */ },
)
// Overflow
Text(
text = longText,
maxLines = 2,
overflow = TextOverflow.Ellipsis,
)TextField
// Basic text field
var text by remember { mutableStateOf("") }
TextField(
value = text,
onValueChange = { text = it },
label = { Text("Enter text") },
)
// Outlined text field (M3 default)
OutlinedTextField(
value = text,
onValueChange = { text = it },
label = { Text("Email") },
leadingIcon = { Icon(Icons.Default.Email, contentDescription = null) },
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Email),
singleLine = true,
)
// Password field
var password by remember { mutableStateOf("") }
var passwordVisible by remember { mutableStateOf(false) }
OutlinedTextField(
value = password,
onValueChange = { password = it },
label = { Text("Password") },
visualTransformation = if (passwordVisible) VisualTransformation.None else PasswordVisualTransformation(),
trailingIcon = {
IconButton(onClick = { passwordVisible = !passwordVisible }) {
Icon(
if (passwordVisible) Icons.Default.Visibility else Icons.Default.VisibilityOff,
contentDescription = if (passwordVisible) "Hide password" else "Show password"
)
}
},
)
// Error state
OutlinedTextField(
value = email,
onValueChange = { email = it },
label = { Text("Email") },
isError = !isValidEmail,
supportingText = { if (!isValidEmail) Text("Invalid email address") },
)Button variants
// Filled (primary)
Button(onClick = { /* ... */ }) { Text("Save") }
// Elevated
ElevatedButton(onClick = { /* ... */ }) { Text("Edit") }
// Outlined
OutlinedButton(onClick = { /* ... */ }) { Text("Cancel") }
// Text
TextButton(onClick = { /* ... */ }) { Text("Learn more") }
// Icon button
IconButton(onClick = { /* ... */ }) {
Icon(Icons.Default.Favorite, contentDescription = "Favorite")
}
// Filled tonal
FilledTonalButton(onClick = { /* ... */ }) { Text("Secondary action") }
// With icon
Button(onClick = { /* ... */ }) {
Icon(Icons.Default.Save, contentDescription = null)
Spacer(Modifier.width(8.dp))
Text("Save")
}Image
// Resource image
Image(
painter = painterResource(R.drawable.my_image),
contentDescription = "My image",
modifier = Modifier.size(120.dp),
contentScale = ContentScale.Crop,
)
// URL image (with Coil)
AsyncImage(
model = "https://example.com/photo.jpg",
contentDescription = "Photo",
modifier = Modifier.size(120.dp).clip(CircleShape),
contentScale = ContentScale.Crop,
)
// Icon
Icon(
Icons.Default.Star,
contentDescription = "Star",
tint = MaterialTheme.colorScheme.primary,
modifier = Modifier.size(24.dp),
)LazyColumn / LazyRow
// Vertical list
LazyColumn(
contentPadding = PaddingValues(horizontal = 16.dp, vertical = 8.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
) {
// Static items
item { Header(text = "My List") }
// Dynamic items
items(users, key = { it.id }) { user ->
UserRow(user = user)
}
// Indexed items
itemsIndexed(items) { index, item ->
Text("Item $index: $item")
}
item { Footer() }
}
// Horizontal list
LazyRow(
horizontalArrangement = Arrangement.spacedBy(8.dp),
contentPadding = PaddingValues(horizontal = 16.dp),
) {
items(categories) { category ->
CategoryChip(category)
}
}
// Grid (LazyVerticalGrid)
LazyVerticalGrid(
columns = GridCells.Fixed(2),
contentPadding = PaddingValues(8.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
) {
items(photos) { photo ->
PhotoCard(photo)
}
}Scaffold
@Composable
fun AppScreen() {
val snackbarHostState = remember { SnackbarHostState() }
Scaffold(
topBar = {
TopAppBar(
title = { Text("My App") },
navigationIcon = {
IconButton(onClick = { navController.popBackStack() }) {
Icon(Icons.AutoMirrored.Filled.ArrowBack, "Back")
}
},
)
},
bottomBar = {
NavigationBar {
NavigationBarItem(
selected = currentRoute == "home",
onClick = { navController.navigate("home") },
icon = { Icon(Icons.Default.Home, "Home") },
label = { Text("Home") },
)
NavigationBarItem(
selected = currentRoute == "profile",
onClick = { navController.navigate("profile") },
icon = { Icon(Icons.Default.Person, "Profile") },
label = { Text("Profile") },
)
}
},
floatingActionButton = {
FloatingActionButton(onClick = { /* add */ }) {
Icon(Icons.Default.Add, "Add")
}
},
snackbarHost = { SnackbarHost(snackbarHostState) },
) { innerPadding ->
Column(modifier = Modifier.padding(innerPadding)) {
// Screen content
}
}
}Pull-to-refresh
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun RefreshableList(viewModel: MyViewModel) {
val isRefreshing by viewModel.isRefreshing.collectAsStateWithLifecycle()
PullToRefreshBox(
isRefreshing = isRefreshing,
onRefresh = { viewModel.refresh() },
) {
LazyColumn {
items(viewModel.items) { item ->
ItemRow(item)
}
}
}
}Switch, Checkbox, Slider, Progress
// Switch
var enabled by remember { mutableStateOf(true) }
Switch(checked = enabled, onCheckedChange = { enabled = it })
// Checkbox
var checked by remember { mutableStateOf(false) }
Checkbox(checked = checked, onCheckedChange = { checked = it })
// Tri-state checkbox
val state = remember { mutableStateOf(ToggleableState.Indeterminate) }
TriStateCheckbox(state = state.value, onClick = { /* cycle state */ })
// Slider
var value by remember { mutableStateOf(0.5f) }
Slider(value = value, onValueChange = { value = it })
// Linear progress
LinearProgressIndicator(progress = { 0.7f })
// Indeterminate
LinearProgressIndicator()
// Circular progress
CircularProgressIndicator(progress = { 0.7f })
CircularProgressIndicator() // indeterminate