What you'll learn
Apps are a conversation with the user, and input is how they talk back. Now that you understand state and hoisting, capturing input is straightforward: an input control shows a value and reports changes — you already know this shape. We'll cover every common control and finish with a validated login form.
By the end you'll be able to:
- Handle taps with
Buttonand its variants - Capture typing with
TextFieldandOutlinedTextField - Use
Checkbox,Switch, andSlider - Assemble a form with live validation and a disabled submit button
Buttons & clicks
A Button takes an onClick lambda and a content slot for its label. Compose ships several variants that express different emphasis — use a filled button for the main action, outlined or text buttons for secondary ones:
Button(onClick = { /* do something */ }) {
Text("Save")
}
// Variants for different emphasis:
OutlinedButton(onClick = {}) { Text("Cancel") } // medium
TextButton(onClick = {}) { Text("Learn more") } // low
IconButton(onClick = {}) { Icon(Icons.Default.Favorite, contentDescription = "Like") }Text fields
A text field is the canonical hoisted composable: give it value and onValueChange. Prefer OutlinedTextField for its clear border. The keyboardOptions parameter tailors the on-screen keyboard — an email keyboard, a number pad, and so on — a small touch that makes an app feel polished:
@Composable
fun EmailField() {
var email by remember { mutableStateOf("") }
OutlinedTextField(
value = email,
onValueChange = { email = it },
label = { Text("Email") },
singleLine = true,
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Email),
)
}Watch out
onValueChange, always store the new value Compose hands you (the it). Forget to update the state and the field appears frozen — you type but nothing shows, because the field only ever displays whatever is in value. It's the most common beginner text-field bug.Checkbox, Switch & Slider
Boolean and numeric inputs follow the exact same pattern — a value plus a change callback:
var checked by remember { mutableStateOf(false) }
Checkbox(checked = checked, onCheckedChange = { checked = it })
var on by remember { mutableStateOf(true) }
Switch(checked = on, onCheckedChange = { on = it })
var volume by remember { mutableStateOf(0.5f) }
Slider(value = volume, onValueChange = { volume = it }) // 0f..1fBuilding a form
A form is just several inputs sharing one screen, plus some rules. The trick is derived state: compute validation directly from the input values. Because those values are Compose state, the derived flags recompute automatically on every keystroke — so the submit button enables itself the moment the form becomes valid, with no manual bookkeeping:
@Composable
fun LoginForm() {
var email by remember { mutableStateOf("") }
var password by remember { mutableStateOf("") }
// Derived validation — recomputed automatically as inputs change.
val emailValid = "@" in email && "." in email
val canSubmit = emailValid && password.length >= 6
Column(verticalArrangement = Arrangement.spacedBy(12.dp)) {
OutlinedTextField(
value = email,
onValueChange = { email = it },
label = { Text("Email") },
isError = email.isNotEmpty() && !emailValid,
singleLine = true,
)
OutlinedTextField(
value = password,
onValueChange = { password = it },
label = { Text("Password") },
visualTransformation = PasswordVisualTransformation(),
singleLine = true,
)
Button(onClick = { /* submit */ }, enabled = canSubmit) {
Text("Log in")
}
}
}Tip
enabled = canSubmit) rather than letting a user submit garbage and then scolding them. Combined with isError on the field, the UI guides people toward a valid entry instead of punishing mistakes.Recap & quick check
Key takeaways
- Button takes onClick and a content slot; use filled/outlined/text variants for different emphasis.
- OutlinedTextField takes value + onValueChange — always store the new value in the callback.
- keyboardOptions tailors the on-screen keyboard (email, number, etc.).
- Checkbox, Switch, and Slider all follow the value + onChange pattern.
- Compute validation as derived state from the inputs; use it to enable the submit button and show errors.
Quick check
1. You type in an OutlinedTextField but nothing appears. Most likely cause?
2. Which button variant signals the primary, high-emphasis action?
3. How do you show an email keyboard for a field?
4. A Slider's value uses what range by default?
5. Best way to stop invalid form submission?
You can capture any input now. Next we tackle showing lots of data efficiently with LazyColumn and LazyRow — the right way to build scrolling lists.