What You Will Build
A travel booking screen with a search bar, destination cards showing location details and per-person pricing, and category icons. This is the standard layout for travel, hotel booking, and flight search applications.
Why Search-and-List UIs Are Fundamental
Most apps follow the search-bar-plus-results pattern. This tutorial teaches how to combine OutlinedTextField with a leading icon as a search bar, and LazyColumn with rich Card items showing multiple data fields in an organized Row/Column hierarchy.
The Destination Data Model
data class Dest(
val name: String,
val country: String,
val price: String,
val icon: ImageVector
)
val destinations = listOf(
Dest("Paris", "France", "$899", Icons.Default.Flight),
Dest("Tokyo", "Japan", "$1,299", Icons.Default.Flight),
Dest("New York", "USA", "$599", Icons.Default.Flight),
Dest("Bali", "Indonesia", "$799", Icons.Default.BeachAccess)
)
The Search Bar
OutlinedTextField(
value = "",
onValueChange = {},
modifier = Modifier.fillMaxWidth(),
placeholder = { Text("Where do you want to go?") },
leadingIcon = { Icon(Icons.Default.Search, null) },
shape = RoundedCornerShape(12.dp)
)
Destination Cards
LazyColumn(
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
items(destinations) { dest ->
Card(Modifier.fillMaxWidth()) {
Row(
Modifier.padding(16.dp),
verticalAlignment = Alignment.CenterVertically
) {
Box(
Modifier
.size(60.dp)
.clip(RoundedCornerShape(12.dp))
.background(
MaterialTheme.colorScheme.primaryContainer
),
contentAlignment = Alignment.Center
) {
Icon(dest.icon, null)
}
Spacer(Modifier.width(16.dp))
Column(Modifier.weight(1f)) {
Text(dest.name, fontWeight = FontWeight.Bold)
Text(dest.country,
color = MaterialTheme.colorScheme.onSurfaceVariant)
}
Column(horizontalAlignment = Alignment.End) {
Text(dest.price,
fontWeight = FontWeight.Bold,
color = MaterialTheme.colorScheme.primary)
Text("per person", fontSize = 11.sp,
color = MaterialTheme.colorScheme.onSurfaceVariant)
}
}
}
}
}
Tips and Pitfalls
- RoundedCornerShape(12.dp) on the search bar gives it a modern pill-like appearance instead of the default rectangle.
- Arrangement.spacedBy(8.dp) on LazyColumn adds consistent spacing between cards without manual Spacers.
- Three-column Row layout: Icon | Name+Country | Price+Label is the standard pattern for list items with mixed content.
- For production: Connect the search bar to a ViewModel with debounced search and filter the destination list reactively.
Minimum SDK: API 24+ with Compose BOM 2024.01+
Min SDK: API 21+ with Compose 1.0+