What You Will Build
A browser-style interface with a Safari-inspired bottom navigation bar featuring a URL input field, navigation arrows, share button, bookmarks, and tab switcher icons. This pattern applies to any app with a bottom toolbar: browsers, document viewers, or media players with transport controls.
Why This Pattern Matters
Bottom-anchored toolbars are a mobile UX best practice because they keep controls within thumb reach. Building a multi-section bottom bar with a text input (URL bar) and icon buttons teaches you Scaffold's bottomBar slot and how to compose complex navigation chrome.
Key Concepts
- Scaffold bottomBar slot for persistent bottom navigation.
- Surface with RoundedCornerShape for the floating URL bar.
- Row with SpaceBetween arrangement for evenly distributed toolbar icons.
- Dark color scheme for a native browser feel.
The Safari Tab Bar Screen
@Composable
fun SafariTabBarScreen() {
var urlText by remember { mutableStateOf("apple.com") }
Scaffold(
containerColor = MaterialTheme.colorScheme.background,
bottomBar = {
Column {
// URL bar
Surface(
modifier = Modifier.fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 4.dp),
shape = RoundedCornerShape(12.dp),
color = MaterialTheme.colorScheme.surfaceVariant
) {
Row(
modifier = Modifier.padding(
horizontal = 12.dp, vertical = 10.dp
),
verticalAlignment = Alignment.CenterVertically
) {
Icon(Icons.Default.Lock, null,
tint = Color.Gray,
modifier = Modifier.size(16.dp))
Spacer(Modifier.width(8.dp))
Text(urlText,
modifier = Modifier.weight(1f),
textAlign = TextAlign.Center,
color = Color.Gray, fontSize = 14.sp)
Icon(Icons.Default.Refresh, null,
tint = Color.Gray,
modifier = Modifier.size(16.dp))
}
}
// Safari toolbar icons
Row(
modifier = Modifier.fillMaxWidth()
.padding(horizontal = 24.dp, vertical = 8.dp),
horizontalArrangement = Arrangement.SpaceBetween
) {
listOf(
Icons.Default.ArrowBack,
Icons.Default.ArrowForward,
Icons.Default.Share,
Icons.Default.MenuBook,
Icons.Default.Tab
).forEach { icon ->
IconButton(onClick = {}) {
Icon(icon, null,
tint = Color(0xFF4A90D9))
}
}
}
}
}
) { padding ->
LazyColumn(
modifier = Modifier.fillMaxSize().padding(padding),
contentPadding = PaddingValues(16.dp),
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
item {
Text("Safari Tab Bar",
fontSize = 28.sp, fontWeight = FontWeight.Bold)
Spacer(Modifier.height(8.dp))
}
items(12) { i ->
Card(
shape = RoundedCornerShape(12.dp),
colors = CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.surfaceVariant
)
) {
Column(modifier = Modifier.padding(16.dp)) {
Box(
modifier = Modifier.fillMaxWidth()
.height(120.dp)
.clip(RoundedCornerShape(8.dp))
.background(Color(0xFF4A90D9).copy(alpha = 0.15f)),
contentAlignment = Alignment.Center
) {
Icon(Icons.Default.Web, null,
modifier = Modifier.size(40.dp),
tint = Color(0xFF4A90D9).copy(alpha = 0.5f))
}
Spacer(Modifier.height(8.dp))
Text("Article ${'$'}{i + 1}",
fontWeight = FontWeight.SemiBold)
Text("A brief description of web content",
fontSize = 13.sp, color = Color.Gray)
}
}
}
}
}
}
Tips and Pitfalls
- Scaffold bottomBar: The bottomBar slot automatically handles safe area insets for the navigation bar on gesture-navigation devices.
- URL bar as Surface: Using Surface with a shape gives the rounded pill appearance without needing a TextField, keeping it lightweight for display-only URLs.
- Icon list pattern: Iterating over a list of icons with
forEachkeeps the toolbar DRY and easy to modify. - Editable URL: Replace the Text with a
TextFieldwithsingleLine = trueand transparent background for an editable address bar.
Minimum SDK: API 21+ with Compose BOM