Text Fields
Text fields allow users to input text with animated label and underline.
Overview
VetraTextField features elegant animations:
- Animated underline expands from center on focus
- Floating label scales and slides smoothly
- Placeholder fades in after label floats
- Clear visual states (focused, error, disabled)
var text by remember { mutableStateOf("") }
VetraTextField(
value = text,
onValueChange = { text = it },
label = "Username",
placeholder = "Enter your username"
)
States
Common Patterns
Form Fields
Column(verticalArrangement = Arrangement.spacedBy(16.dp)) {
VetraTextField(
value = name,
onValueChange = { name = it },
label = "Full Name",
placeholder = "John Doe"
)
VetraTextField(
value = email,
onValueChange = { email = it },
label = "Email",
placeholder = "john@example.com",
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Email
)
)
VetraTextField(
value = password,
onValueChange = { password = it },
label = "Password",
visualTransformation = PasswordVisualTransformation(),
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Password
)
)
}
Search Field
VetraTextField(
value = query,
onValueChange = { query = it },
placeholder = "Search...", // (1)!
leadingIcon = {
Icon(Icons.Default.Search, "Search")
},
trailingIcon = {
if (query.isNotEmpty()) {
IconButton(onClick = { query = "" }) {
Icon(Icons.Default.Clear, "Clear")
}
}
}
)
- No label for cleaner search UI
Validation
var email by remember { mutableStateOf("") }
val isValidEmail = remember(email) {
email.matches(Regex("[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}"))
}
VetraTextField(
value = email,
onValueChange = { email = it },
label = "Email",
isError = email.isNotEmpty() && !isValidEmail,
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Email)
)
if (email.isNotEmpty() && !isValidEmail) {
Text(
text = "Please enter a valid email",
color = VetraTheme.colors.danger,
style = VetraTheme.typography.bodySm,
modifier = Modifier.padding(start = 16.dp, top = 4.dp)
)
}
Multiline Input
VetraTextField(
value = description,
onValueChange = { description = it },
label = "Description",
placeholder = "Tell us more...",
singleLine = false,
maxLines = 5
)
Icons
Leading Icon
Use for input type indication:
VetraTextField(
value = email,
onValueChange = { email = it },
label = "Email",
leadingIcon = {
Icon(
imageVector = Icons.Default.Email,
contentDescription = null,
tint = VetraTheme.colors.textSecondary
)
}
)
Trailing Icon
Use for actions or status:
VetraTextField(
value = password,
onValueChange = { password = it },
label = "Password",
visualTransformation = if (showPassword) {
VisualTransformation.None
} else {
PasswordVisualTransformation()
},
trailingIcon = {
IconButton(onClick = { showPassword = !showPassword }) {
Icon(
if (showPassword) Icons.Default.VisibilityOff
else Icons.Default.Visibility,
"Toggle visibility"
)
}
}
)
Keyboard Options
// Email
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Email,
imeAction = ImeAction.Next
)
// Phone
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Phone
)
// Number
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Number
)
Best Practices
Do
- ✅ Use descriptive labels
- ✅ Provide helpful placeholders
- ✅ Show clear error messages
- ✅ Use appropriate keyboard types
- ✅ Validate input appropriately
- ✅ Disable when not editable
Don't
- ❌ Use placeholder as sole label
- ❌ Show errors before user finishes typing
- ❌ Use unclear error messages
- ❌ Make required fields unclear
- ❌ Forget to handle keyboard actions
- ❌ Use tiny text or inadequate spacing
Accessibility
- Label Position: Floating label stays visible with content
- Error States: Clear visual indication with color and message
- Focus: Animated underline shows focus state
- Keyboard: Proper keyboard types for input context
- Screen Readers: Labels are properly announced
Improve Accessibility
- Always provide labels for screen readers
- Use descriptive error messages
- Ensure adequate touch targets for trailing icons
- Test with keyboard navigation
API Reference
VetraTextField
| Parameter | Type | Default | Description |
|---|---|---|---|
value |
String |
Required | Current text value |
onValueChange |
(String) -> Unit |
Required | Callback when text changes |
modifier |
Modifier |
Modifier |
Modifier for customization |
enabled |
Boolean |
true |
Whether field is editable |
readOnly |
Boolean |
false |
Whether field is read-only |
label |
String? |
null |
Floating label text |
placeholder |
String? |
null |
Placeholder text (shows after label floats) |
leadingIcon |
@Composable (() -> Unit)? |
null |
Icon before text |
trailingIcon |
@Composable (() -> Unit)? |
null |
Icon after text |
isError |
Boolean |
false |
Whether field is in error state |
visualTransformation |
VisualTransformation |
None |
Transform display (e.g., password) |
keyboardOptions |
KeyboardOptions |
Default |
Keyboard configuration |
keyboardActions |
KeyboardActions |
Default |
Keyboard action handlers |
singleLine |
Boolean |
true |
Single or multiline input |
maxLines |
Int |
1 if single, Int.MAX_VALUE if multi |
Maximum lines |
Default Dimensions
| Property | Value | Note |
|---|---|---|
| Min Height | 58dp | Comfortable touch target |
| Horizontal Padding | 16dp | Internal spacing |
| Vertical Padding | 20dp | Internal spacing |
| Underline Width | 2dp | Focus indicator |
| Corner Radius | 8dp | shapes.sm |
Animation Timing
| Animation | Duration | Easing | Description |
|---|---|---|---|
| Underline Expansion | 300ms | EaseInOutCubic | Expands from center |
| Label Float | 300ms | EaseOut | Scales and moves up |
| Placeholder Fade | 250ms | EaseOut | Fades in after label |
| Color Transition | 300ms | EaseInOutCubic | Smooth color changes |
Color States
| State | Underline | Label | Text |
|---|---|---|---|
| Default | border |
textSecondary |
textPrimary |
| Focused | brand |
textSecondary |
textPrimary |
| Error | danger |
danger |
textPrimary |
| Disabled | border |
textDisabled |
textDisabled |
See Also:
- Buttons - Form submission buttons
- Design: Colors - Text field colors
- Design: Typography - Text styles