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+

  • Animated headers: You can animate header opacity or height by reading the LazyListState to detect when a header becomes sticky.
  • Min SDK: API 21+ with Compose 1.0+

    Get New Tutorials by Email

    No spam. Just clear, practical breakdowns you can apply right away.

    Enjoy this tutorial?

    Get new practical tech tutorials in your inbox.