Compose ships Material 3 components — buttons, text fields, cards, dialogs — that look right out of the box. Learn the common elements and how a MaterialTheme gives your whole app consistent color and typography.
Compose Material 3 provides ready-made, accessible components matching Google’s design system: buttons, text fields, cards, icons, switches, and more. You assemble screens from these rather than styling raw shapes, getting correct touch targets, ripples, and states for free. Knowing the handful you use daily — Button, Text, TextField, Card, Icon — covers most UI.
@Composable
fun ContactCard() {
Card(modifier = Modifier.padding(16.dp)) {
Column(Modifier.padding(16.dp)) {
Text("Ada Lovelace", style = MaterialTheme.typography.titleMedium)
Text("ada@example.com", style = MaterialTheme.typography.bodyMedium)
Row {
Button(onClick = { }) { Text("Call") }
Spacer(Modifier.width(8.dp))
OutlinedButton(onClick = { }) { Text("Email") }
}
}
}
}
// Card, Text, Button, OutlinedButton — accessible + Material-correct out of the box.TextField and OutlinedTextField collect text, and — following state hoisting — they are controlled: you pass the current value and an onValueChange callback, holding the text in your own state. You configure the keyboard type, label, placeholder, and visual transformation (like password masking). This controlled pattern keeps input state explicit and testable.
@Composable
fun LoginForm() {
var email by remember { mutableStateOf("") }
var password by remember { mutableStateOf("") }
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
OutlinedTextField(
value = email, onValueChange = { email = it },
label = { Text("Email") },
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Email)
)
OutlinedTextField(
value = password, onValueChange = { password = it },
label = { Text("Password") },
visualTransformation = PasswordVisualTransformation() // masks input
)
}
// controlled inputs: you own the state, the field reports changes. Hoistable.
}Transient UI — alert dialogs, bottom sheets, snackbars — is shown conditionally based on state: a boolean controls whether the dialog composable is in the tree. This is the declarative pattern for popups: rather than imperatively "showing" a dialog, you include it when a state says to. It fits naturally with the state model and keeps such UI predictable.
@Composable
fun DeleteButton() {
var showDialog by remember { mutableStateOf(false) }
Button(onClick = { showDialog = true }) { Text("Delete") }
if (showDialog) { // the dialog exists only when state says so
AlertDialog(
onDismissRequest = { showDialog = false },
title = { Text("Delete item?") },
confirmButton = {
TextButton(onClick = { /* delete */; showDialog = false }) { Text("Delete") }
},
dismissButton = {
TextButton(onClick = { showDialog = false }) { Text("Cancel") }
}
)
}
}
// declarative popups: a boolean state decides whether the dialog is composed.A MaterialTheme defines your app’s color scheme, typography, and shapes once at the root, and every Material component reads from it — so a single theme gives the whole app consistent styling and effortless dark mode. Referencing MaterialTheme.colorScheme and .typography instead of hardcoding colors and fonts is what makes an app cohesive and themeable.
@Composable
fun MyAppTheme(darkTheme: Boolean = isSystemInDarkTheme(), content: @Composable () -> Unit) {
MaterialTheme(
colorScheme = if (darkTheme) darkColorScheme() else lightColorScheme(),
typography = Typography(), // your type scale
content = content
)
}
// components read the theme automatically -> app-wide consistency + dark mode:
@Composable
fun Title() {
Text(
"Welcome",
color = MaterialTheme.colorScheme.primary, // not a hardcoded color
style = MaterialTheme.typography.headlineMedium
)
}
// wrap setContent { MyAppTheme { ... } } once; the whole app is themed.