Material Design components for Compose Multiplatform, specifically optimized for iOS devices running on ARM64 architecture
—
Material Design navigation components that integrate seamlessly with iOS navigation patterns and UIKit, providing consistent Material styling while respecting iOS platform conventions.
Material design layout structure that provides slots for common screen elements with iOS-specific adaptations.
/**
* Material Design layout structure providing a framework for common Material Design components
* Integrates with iOS navigation patterns and safe area handling
* @param modifier Modifier to be applied to this Scaffold
* @param scaffoldState State of this scaffold containing the DrawerState and SnackbarHostState
* @param topBar Top app bar of the screen, typically a TopAppBar
* @param bottomBar Bottom bar of the screen, typically a BottomNavigation
* @param snackbarHost Component to host Snackbars
* @param floatingActionButton Main action button, typically a FloatingActionButton
* @param floatingActionButtonPosition Position of the FAB on the screen
* @param isFloatingActionButtonDocked Whether the FAB should dock with the bottom bar
* @param drawerContent Content of the Drawer sheet that can be pulled from the left side
* @param drawerGesturesEnabled Whether or not drawer can be interacted with via gestures
* @param drawerShape Shape of the drawer sheet
* @param drawerElevation Elevation of the drawer sheet
* @param drawerBackgroundColor Background color of the drawer sheet
* @param drawerContentColor Color of the content inside the drawer sheet
* @param drawerScrimColor Color of the scrim that obscures content when the drawer is open
* @param backgroundColor Background color of the scaffold body
* @param contentColor Preferred content color provided by scaffold to its children
* @param content Content of the screen
*/
@Composable
fun Scaffold(
modifier: Modifier = Modifier,
scaffoldState: ScaffoldState = rememberScaffoldState(),
topBar: @Composable () -> Unit = {},
bottomBar: @Composable () -> Unit = {},
snackbarHost: @Composable (SnackbarHostState) -> Unit = { SnackbarHost(it) },
floatingActionButton: @Composable () -> Unit = {},
floatingActionButtonPosition: FabPosition = FabPosition.End,
isFloatingActionButtonDocked: Boolean = false,
drawerContent: @Composable (ColumnScope.() -> Unit)? = null,
drawerGesturesEnabled: Boolean = true,
drawerShape: Shape = MaterialTheme.shapes.large,
drawerElevation: Dp = DrawerDefaults.Elevation,
drawerBackgroundColor: Color = MaterialTheme.colors.surface,
drawerContentColor: Color = contentColorFor(drawerBackgroundColor),
drawerScrimColor: Color = DrawerDefaults.scrimColor,
backgroundColor: Color = MaterialTheme.colors.background,
contentColor: Color = contentColorFor(backgroundColor),
content: @Composable (PaddingValues) -> Unit
)Usage Examples:
// Basic scaffold with iOS-style navigation
Scaffold(
topBar = {
TopAppBar(
title = { Text("My App") },
navigationIcon = {
IconButton(onClick = { /* handle back */ }) {
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back")
}
}
)
},
floatingActionButton = {
FloatingActionButton(
onClick = { /* add action */ }
) {
Icon(Icons.Default.Add, contentDescription = "Add")
}
}
) { paddingValues ->
LazyColumn(
modifier = Modifier.padding(paddingValues),
contentPadding = PaddingValues(16.dp)
) {
items(items) { item ->
// List content
}
}
}
// Scaffold with drawer (iOS-style sidebar)
val scaffoldState = rememberScaffoldState()
val scope = rememberCoroutineScope()
Scaffold(
scaffoldState = scaffoldState,
topBar = {
TopAppBar(
title = { Text("With Drawer") },
navigationIcon = {
IconButton(
onClick = {
scope.launch {
scaffoldState.drawerState.open()
}
}
) {
Icon(Icons.Default.Menu, contentDescription = "Menu")
}
}
)
},
drawerContent = {
DrawerContent()
}
) { paddingValues ->
// Main content
}
// Scaffold with bottom navigation
Scaffold(
bottomBar = {
BottomNavigation {
val navItems = listOf("Home", "Search", "Profile")
navItems.forEachIndexed { index, item ->
BottomNavigationItem(
icon = { Icon(getIconForTab(index), contentDescription = null) },
label = { Text(item) },
selected = selectedTab == index,
onClick = { selectedTab = index }
)
}
}
}
) { paddingValues ->
// Content for each tab
}Material app bar for screen headers with iOS navigation integration and safe area handling.
/**
* Material Design top app bar with iOS-style navigation integration
* @param title The title to be displayed in the center of the TopAppBar
* @param modifier Modifier to be applied to this TopAppBar
* @param navigationIcon The navigation icon displayed at the start of the TopAppBar
* @param actions The actions displayed at the end of the TopAppBar
* @param backgroundColor The background color for the TopAppBar
* @param contentColor The preferred content color provided by this TopAppBar to its children
* @param elevation The elevation of this TopAppBar
*/
@Composable
fun TopAppBar(
title: @Composable () -> Unit,
modifier: Modifier = Modifier,
navigationIcon: @Composable (() -> Unit)? = null,
actions: @Composable RowScope.() -> Unit = {},
backgroundColor: Color = MaterialTheme.colors.primarySurface,
contentColor: Color = contentColorFor(backgroundColor),
elevation: Dp = AppBarDefaults.TopAppBarElevation
)Usage Examples:
// Basic top app bar
TopAppBar(
title = { Text("Screen Title") }
)
// Top app bar with navigation and actions
TopAppBar(
title = { Text("Messages") },
navigationIcon = {
IconButton(onClick = { /* navigate back */ }) {
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back")
}
},
actions = {
IconButton(onClick = { /* search */ }) {
Icon(Icons.Default.Search, contentDescription = "Search")
}
IconButton(onClick = { /* more options */ }) {
Icon(Icons.Default.MoreVert, contentDescription = "More")
}
}
)
// Custom styled top app bar (iOS-style)
TopAppBar(
title = {
Text(
"Custom Title",
style = MaterialTheme.typography.h6.copy(fontWeight = FontWeight.Medium)
)
},
backgroundColor = Color.Transparent,
contentColor = MaterialTheme.colors.onBackground,
elevation = 0.dp
)TopAppBar defaults and styling configurations for consistent Material Design appearance with iOS adaptations.
object TopAppBarDefaults {
/**
* Creates TopAppBarColors with Material 3 theming for iOS optimization
*/
@Composable
fun centerAlignedTopAppBarColors(
containerColor: Color = MaterialTheme.colorScheme.surface,
scrolledContainerColor: Color = MaterialTheme.colorScheme.applyTonalElevation(
backgroundColor = containerColor,
elevation = 3.dp
),
navigationIconContentColor: Color = MaterialTheme.colorScheme.onSurface,
titleContentColor: Color = MaterialTheme.colorScheme.onSurface,
actionIconContentColor: Color = MaterialTheme.colorScheme.onSurfaceVariant
): TopAppBarColors
/**
* Creates TopAppBarColors for medium-sized top app bars
*/
@Composable
fun mediumTopAppBarColors(
containerColor: Color = MaterialTheme.colorScheme.surface,
scrolledContainerColor: Color = MaterialTheme.colorScheme.applyTonalElevation(
backgroundColor = containerColor,
elevation = 3.dp
),
navigationIconContentColor: Color = MaterialTheme.colorScheme.onSurface,
titleContentColor: Color = MaterialTheme.colorScheme.onSurface,
actionIconContentColor: Color = MaterialTheme.colorScheme.onSurfaceVariant
): TopAppBarColors
/**
* Creates TopAppBarScrollBehavior for iOS-style scroll behavior
*/
@Composable
fun pinnedScrollBehavior(
state: TopAppBarState = rememberTopAppBarState(),
canScroll: () -> Boolean = { true }
): TopAppBarScrollBehavior
@Composable
fun enterAlwaysScrollBehavior(
state: TopAppBarState = rememberTopAppBarState(),
canScroll: () -> Boolean = { true },
snapAnimationSpec: AnimationSpec<Float>? = spring(stiffness = Spring.StiffnessMediumLow),
flingAnimationSpec: DecayAnimationSpec<Float>? = rememberSplineBasedDecay()
): TopAppBarScrollBehavior
}
interface TopAppBarColors {
@Composable
fun containerColor(scrollFraction: Float): State<Color>
@Composable
fun navigationIconContentColor(): State<Color>
@Composable
fun titleContentColor(): State<Color>
@Composable
fun actionIconContentColor(): State<Color>
}
interface TopAppBarScrollBehavior {
val state: TopAppBarState
val isPinned: Boolean
val snapAnimationSpec: AnimationSpec<Float>?
val flingAnimationSpec: DecayAnimationSpec<Float>?
val nestedScrollConnection: NestedScrollConnection
}
@Stable
class TopAppBarState(
heightOffsetLimit: Float,
initialHeightOffset: Float = 0f,
initialContentOffset: Float = 0f
) {
val heightOffset: Float
val contentOffset: Float
val overlappedFraction: Float
}
@Composable
fun rememberTopAppBarState(
initialHeightOffsetLimit: Float = -Float.MAX_VALUE,
initialHeightOffset: Float = 0f,
initialContentOffset: Float = 0f
): TopAppBarStateUsage Examples:
// TopAppBar with custom colors (iOS-style)
TopAppBar(
title = { Text("My App") },
colors = TopAppBarDefaults.centerAlignedTopAppBarColors(
containerColor = MaterialTheme.colorScheme.primaryContainer,
titleContentColor = MaterialTheme.colorScheme.onPrimaryContainer,
navigationIconContentColor = MaterialTheme.colorScheme.onPrimaryContainer
),
navigationIcon = {
IconButton(onClick = { /* back navigation */ }) {
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back")
}
}
)
// TopAppBar with scroll behavior
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
Scaffold(
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
topBar = {
TopAppBar(
title = { Text("Scrolling App Bar") },
scrollBehavior = scrollBehavior
)
}
) { paddingValues ->
LazyColumn(
modifier = Modifier.fillMaxSize(),
contentPadding = paddingValues
) {
items(100) { index ->
Text(
text = "Item $index",
modifier = Modifier.padding(16.dp)
)
}
}
}
// Medium-sized TopAppBar with custom colors
MediumTopAppBar(
title = { Text("Large Title") },
colors = TopAppBarDefaults.mediumTopAppBarColors(
containerColor = MaterialTheme.colorScheme.surface,
titleContentColor = MaterialTheme.colorScheme.primary
),
scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior()
)Bottom navigation bar container with iOS tab bar styling and behavior.
/**
* Material Design bottom navigation with iOS tab bar adaptations
* @param modifier Modifier to be applied to this BottomNavigation
* @param backgroundColor The background color for this BottomNavigation
* @param contentColor The preferred content color provided by this BottomNavigation to its children
* @param elevation The elevation of this BottomNavigation
* @param content The content of this BottomNavigation, typically BottomNavigationItems
*/
@Composable
fun BottomNavigation(
modifier: Modifier = Modifier,
backgroundColor: Color = MaterialTheme.colors.primarySurface,
contentColor: Color = contentColorFor(backgroundColor),
elevation: Dp = BottomNavigationDefaults.Elevation,
content: @Composable RowScope.() -> Unit
)Individual bottom navigation items with iOS-style selection states and animations.
/**
* Material Design bottom navigation item with iOS tab styling
* @param selected Whether this item is selected
* @param onClick Called when this item is clicked
* @param icon The icon for this item, typically an Icon
* @param modifier Modifier to be applied to this item
* @param enabled Controls the enabled state of this item
* @param label Optional text label for this item
* @param alwaysShowLabel Whether to always show the label for this item
* @param selectedContentColor The color of the content when this item is selected
* @param unselectedContentColor The color of the content when this item is not selected
*/
@Composable
fun RowScope.BottomNavigationItem(
selected: Boolean,
onClick: () -> Unit,
icon: @Composable () -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
label: @Composable (() -> Unit)? = null,
alwaysShowLabel: Boolean = true,
selectedContentColor: Color = LocalContentColor.current,
unselectedContentColor: Color = selectedContentColor.copy(alpha = ContentAlpha.medium)
)Usage Examples:
// iOS-style bottom navigation
var selectedTab by remember { mutableStateOf(0) }
val tabs = listOf(
Triple("Home", Icons.Default.Home, Icons.Outlined.Home),
Triple("Search", Icons.Default.Search, Icons.Outlined.Search),
Triple("Favorites", Icons.Default.Favorite, Icons.Outlined.FavoriteBorder),
Triple("Profile", Icons.Default.Person, Icons.Outlined.Person)
)
BottomNavigation(
backgroundColor = MaterialTheme.colors.surface,
contentColor = MaterialTheme.colors.primary
) {
tabs.forEachIndexed { index, (title, selectedIcon, unselectedIcon) ->
BottomNavigationItem(
icon = {
Icon(
if (selectedTab == index) selectedIcon else unselectedIcon,
contentDescription = null
)
},
label = { Text(title) },
selected = selectedTab == index,
onClick = { selectedTab = index },
selectedContentColor = MaterialTheme.colors.primary,
unselectedContentColor = MaterialTheme.colors.onSurface.copy(alpha = 0.6f)
)
}
}Bottom app bar with FAB integration and iOS-style positioning.
/**
* Material Design bottom app bar with iOS-optimized FAB integration
* @param modifier Modifier to be applied to this BottomAppBar
* @param backgroundColor The background color for this BottomAppBar
* @param contentColor The preferred content color provided by this BottomAppBar to its children
* @param cutoutShape The shape of the cutout that will be added to the BottomAppBar
* @param elevation The elevation of this BottomAppBar
* @param content The content of this BottomAppBar
*/
@Composable
fun BottomAppBar(
modifier: Modifier = Modifier,
backgroundColor: Color = MaterialTheme.colors.primarySurface,
contentColor: Color = contentColorFor(backgroundColor),
cutoutShape: Shape? = null,
elevation: Dp = AppBarDefaults.BottomAppBarElevation,
content: @Composable RowScope.() -> Unit
)Vertical navigation component for larger iOS devices like iPad.
/**
* Material Design navigation rail optimized for iOS larger screens
* @param modifier Modifier to be applied to this NavigationRail
* @param backgroundColor The background color for this NavigationRail
* @param contentColor The preferred content color provided by this NavigationRail to its children
* @param elevation The elevation of this NavigationRail
* @param header Optional header content displayed at the top
* @param content The content of this NavigationRail, typically NavigationRailItems
*/
@Composable
fun NavigationRail(
modifier: Modifier = Modifier,
backgroundColor: Color = MaterialTheme.colors.surface,
contentColor: Color = contentColorFor(backgroundColor),
elevation: Dp = NavigationRailDefaults.Elevation,
header: @Composable (ColumnScope.() -> Unit)? = null,
content: @Composable ColumnScope.() -> Unit
)Material Design drawer components with iOS-style presentation and gestures.
/**
* State of the DrawerLayout composable
*/
@Stable
class DrawerState(
initialValue: DrawerValue,
confirmStateChange: (DrawerValue) -> Boolean = { true }
) {
val currentValue: DrawerValue
val isAnimationRunning: Boolean
val isClosed: Boolean
val isOpen: Boolean
suspend fun open()
suspend fun close()
suspend fun animateTo(targetValue: DrawerValue, anim: AnimationSpec<Float> = DrawerConstants.defaultAnimationSpec)
suspend fun snapTo(targetValue: DrawerValue)
}
/**
* Create and remember a DrawerState
*/
@Composable
fun rememberDrawerState(
initialValue: DrawerValue,
confirmStateChange: (DrawerValue) -> Boolean = { true }
): DrawerState
enum class DrawerValue {
Closed, Open
}Usage Examples:
// Modal drawer with iOS-style presentation
@Composable
fun ModalDrawerExample() {
val drawerState = rememberDrawerState(DrawerValue.Closed)
val scope = rememberCoroutineScope()
ModalDrawer(
drawerState = drawerState,
drawerShape = RoundedCornerShape(topEnd = 16.dp, bottomEnd = 16.dp),
drawerContent = {
Column(
modifier = Modifier
.fillMaxHeight()
.width(300.dp)
.padding(16.dp)
) {
Text(
"Drawer Header",
style = MaterialTheme.typography.h6,
modifier = Modifier.padding(bottom = 16.dp)
)
Divider()
// Drawer items
repeat(5) { index ->
TextButton(
onClick = {
scope.launch { drawerState.close() }
},
modifier = Modifier.fillMaxWidth()
) {
Text("Menu Item ${index + 1}")
}
}
}
}
) {
// Main content
Scaffold(
topBar = {
TopAppBar(
title = { Text("Drawer Example") },
navigationIcon = {
IconButton(
onClick = {
scope.launch { drawerState.open() }
}
) {
Icon(Icons.Default.Menu, contentDescription = "Menu")
}
}
)
}
) { paddingValues ->
// Screen content
}
}
}object AppBarDefaults {
val TopAppBarElevation: Dp = 4.dp
val BottomAppBarElevation: Dp = 8.dp
}
object BottomNavigationDefaults {
val Elevation: Dp = 8.dp
}
object NavigationRailDefaults {
val Elevation: Dp = 0.dp
}
object DrawerDefaults {
val Elevation: Dp = 16.dp
val scrimColor: Color = Color.Black.copy(alpha = 0.32f)
}
enum class FabPosition {
Center, End
}
@Stable
class ScaffoldState(
val drawerState: DrawerState,
val snackbarHostState: SnackbarHostState
)
@Composable
fun rememberScaffoldState(
drawerState: DrawerState = rememberDrawerState(DrawerValue.Closed),
snackbarHostState: SnackbarHostState = remember { SnackbarHostState() }
): ScaffoldStateMaterial Design tab layout for organizing content into different views with iOS-optimized touch targets.
/**
* Material Design fixed tab row with iOS-optimized spacing and touch targets
* @param selectedTabIndex The index of the currently selected tab
* @param modifier Modifier to be applied to this tab row
* @param backgroundColor The background color for the tab row
* @param contentColor The preferred content color provided by this tab row to its children
* @param indicator The indicator that represents which tab is currently selected
* @param divider The divider displayed at the bottom of the tab row
* @param tabs The tabs inside this tab row, typically this will be multiple Tab components
*/
@Composable
fun TabRow(
selectedTabIndex: Int,
modifier: Modifier = Modifier,
backgroundColor: Color = MaterialTheme.colors.primarySurface,
contentColor: Color = contentColorFor(backgroundColor),
indicator: @Composable (tabPositions: List<TabPosition>) -> Unit = @Composable { tabPositions ->
TabRowDefaults.Indicator(
Modifier.tabIndicatorOffset(tabPositions[selectedTabIndex])
)
},
divider: @Composable () -> Unit = @Composable {
TabRowDefaults.Divider()
},
tabs: @Composable () -> Unit
)
/**
* Material Design scrollable tab row for large numbers of tabs with iOS momentum scrolling
* @param selectedTabIndex The index of the currently selected tab
* @param modifier Modifier to be applied to this tab row
* @param backgroundColor The background color for the tab row
* @param contentColor The preferred content color provided by this tab row to its children
* @param edgePadding The padding between the starting and ending edge of the scrollable tab row
* @param indicator The indicator that represents which tab is currently selected
* @param divider The divider displayed at the bottom of the tab row
* @param tabs The tabs inside this tab row, typically this will be multiple Tab components
*/
@Composable
fun ScrollableTabRow(
selectedTabIndex: Int,
modifier: Modifier = Modifier,
backgroundColor: Color = MaterialTheme.colors.primarySurface,
contentColor: Color = contentColorFor(backgroundColor),
edgePadding: Dp = TabRowDefaults.ScrollableTabRowPadding,
indicator: @Composable (tabPositions: List<TabPosition>) -> Unit = @Composable { tabPositions ->
TabRowDefaults.Indicator(
Modifier.tabIndicatorOffset(tabPositions[selectedTabIndex])
)
},
divider: @Composable () -> Unit = @Composable {
TabRowDefaults.Divider()
},
tabs: @Composable () -> Unit
)
/**
* Material Design tab with iOS-optimized touch feedback and accessibility
* @param selected Whether this tab is currently selected
* @param onClick The callback to be invoked when this tab is selected
* @param modifier Modifier to be applied to this tab
* @param enabled Controls the enabled state of this tab
* @param text The text label displayed in this tab
* @param icon The optional icon displayed in this tab
* @param interactionSource The MutableInteractionSource representing the stream of interactions
* @param selectedContentColor The color of the content when this tab is selected
* @param unselectedContentColor The color of the content when this tab is not selected
*/
@Composable
fun Tab(
selected: Boolean,
onClick: () -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
text: @Composable (() -> Unit)? = null,
icon: @Composable (() -> Unit)? = null,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
selectedContentColor: Color = LocalContentColor.current,
unselectedContentColor: Color = selectedContentColor.copy(alpha = ContentAlpha.medium)
)
/**
* LeadingIconTab with both icon and text arranged horizontally
*/
@Composable
fun LeadingIconTab(
selected: Boolean,
onClick: () -> Unit,
text: @Composable () -> Unit,
icon: @Composable () -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
selectedContentColor: Color = LocalContentColor.current,
unselectedContentColor: Color = selectedContentColor.copy(alpha = ContentAlpha.medium)
)
object TabRowDefaults {
val ScrollableTabRowPadding: Dp = 52.dp
@Composable
fun Indicator(
modifier: Modifier = Modifier,
height: Dp = 2.dp,
color: Color = LocalContentColor.current
)
@Composable
fun Divider(
modifier: Modifier = Modifier,
thickness: Dp = 1.dp,
color: Color = LocalContentColor.current.copy(alpha = 0.12f)
)
}
@Stable
class TabPosition(val left: Dp, val width: Dp) {
val right: Dp get() = left + width
}
fun Modifier.tabIndicatorOffset(
currentTabPosition: TabPosition
): ModifierUsage Examples:
// Basic tab row with text tabs
var selectedTab by remember { mutableStateOf(0) }
val tabs = listOf("Home", "Search", "Profile")
Column {
TabRow(selectedTabIndex = selectedTab) {
tabs.forEachIndexed { index, title ->
Tab(
selected = selectedTab == index,
onClick = { selectedTab = index },
text = { Text(title) }
)
}
}
// Content based on selected tab
when (selectedTab) {
0 -> HomeContent()
1 -> SearchContent()
2 -> ProfileContent()
}
}
// Tab row with icons and text
TabRow(selectedTabIndex = currentTab) {
Tab(
selected = currentTab == 0,
onClick = { currentTab = 0 },
text = { Text("Calls") },
icon = { Icon(Icons.Default.Call, contentDescription = null) }
)
Tab(
selected = currentTab == 1,
onClick = { currentTab = 1 },
text = { Text("Messages") },
icon = { Icon(Icons.Default.Email, contentDescription = null) }
)
Tab(
selected = currentTab == 2,
onClick = { currentTab = 2 },
text = { Text("Contacts") },
icon = { Icon(Icons.Default.Person, contentDescription = null) }
)
}
// Scrollable tab row for many tabs
ScrollableTabRow(
selectedTabIndex = selectedTabIndex,
backgroundColor = MaterialTheme.colors.surface,
contentColor = MaterialTheme.colors.onSurface
) {
categories.forEachIndexed { index, category ->
Tab(
selected = selectedTabIndex == index,
onClick = { selectedTabIndex = index },
text = { Text(category.name) }
)
}
}
// Leading icon tab with horizontal arrangement
LeadingIconTab(
selected = currentTab == 0,
onClick = { currentTab = 0 },
text = { Text("Documents") },
icon = { Icon(Icons.Default.Description, contentDescription = null) }
)Material 3 navigation bar for bottom navigation with modern design language and iOS-optimized behavior.
/**
* Material 3 NavigationBar for bottom navigation with iOS integration
* @param modifier Modifier to be applied to this navigation bar
* @param containerColor The color used for the background of this navigation bar
* @param contentColor The preferred color for content inside this navigation bar
* @param tonalElevation When containerColor is ColorScheme.surface, a translucent primary color overlay is applied
* @param windowInsets A window insets that the navigation bar will respect
* @param content The content of this navigation bar, typically NavigationBarItem components
*/
@Composable
fun NavigationBar(
modifier: Modifier = Modifier,
containerColor: Color = NavigationBarDefaults.containerColor,
contentColor: Color = MaterialTheme.colorScheme.onSurfaceVariant,
tonalElevation: Dp = NavigationBarDefaults.Elevation,
windowInsets: WindowInsets = NavigationBarDefaults.windowInsets,
content: @Composable RowScope.() -> Unit
)
/**
* Material 3 NavigationBarItem with iOS-optimized selection behavior
* @param selected Whether this item is currently selected
* @param onClick Called when this item is clicked
* @param icon The icon for this item
* @param modifier Modifier to be applied to this item
* @param enabled Controls the enabled state of this item
* @param label Optional text label for this item
* @param alwaysShowLabel Whether to always show the label for this item
* @param colors NavigationBarItemColors that will be used to resolve the colors used for this item
* @param interactionSource The MutableInteractionSource representing the stream of interactions
*/
@Composable
fun RowScope.NavigationBarItem(
selected: Boolean,
onClick: () -> Unit,
icon: @Composable () -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
label: @Composable (() -> Unit)? = null,
alwaysShowLabel: Boolean = true,
colors: NavigationBarItemColors = NavigationBarItemDefaults.colors(),
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }
)
object NavigationBarDefaults {
val Elevation: Dp = 3.dp
val containerColor: Color @Composable get() = MaterialTheme.colorScheme.surface
val windowInsets: WindowInsets @Composable get() = WindowInsets.systemBars.only(WindowInsetsSides.Horizontal + WindowInsetsSides.Bottom)
}
object NavigationBarItemDefaults {
@Composable
fun colors(
selectedIconColor: Color = NavigationBarTokens.ActiveIconColor.toColor(),
selectedTextColor: Color = NavigationBarTokens.ActiveLabelTextColor.toColor(),
indicatorColor: Color = NavigationBarTokens.ActiveIndicatorColor.toColor(),
unselectedIconColor: Color = NavigationBarTokens.InactiveIconColor.toColor(),
unselectedTextColor: Color = NavigationBarTokens.InactiveLabelTextColor.toColor(),
disabledIconColor: Color = NavigationBarTokens.InactiveIconColor.toColor().copy(alpha = 0.38f),
disabledTextColor: Color = NavigationBarTokens.InactiveLabelTextColor.toColor().copy(alpha = 0.38f)
): NavigationBarItemColors
}
interface NavigationBarItemColors
interface WindowInsets {
companion object {
val systemBars: WindowInsets
}
fun only(sides: WindowInsetsSides): WindowInsets
}
enum class WindowInsetsSides {
Start, Top, End, Bottom, Horizontal, Vertical, All
}Usage Examples:
// Basic navigation bar with icons and labels
var selectedItem by remember { mutableStateOf(0) }
val items = listOf("Home", "Search", "Profile")
val icons = listOf(Icons.Default.Home, Icons.Default.Search, Icons.Default.Person)
Scaffold(
bottomBar = {
NavigationBar {
items.forEachIndexed { index, item ->
NavigationBarItem(
icon = { Icon(icons[index], contentDescription = null) },
label = { Text(item) },
selected = selectedItem == index,
onClick = { selectedItem = index }
)
}
}
}
) { innerPadding ->
// Screen content
}
// Navigation bar with badges
NavigationBar {
NavigationBarItem(
icon = {
BadgedBox(
badge = { Badge { Text("3") } }
) {
Icon(Icons.Default.Email, contentDescription = null)
}
},
label = { Text("Messages") },
selected = selectedTab == 0,
onClick = { selectedTab = 0 }
)
NavigationBarItem(
icon = { Icon(Icons.Default.Home, contentDescription = null) },
label = { Text("Home") },
selected = selectedTab == 1,
onClick = { selectedTab = 1 }
)
NavigationBarItem(
icon = {
BadgedBox(
badge = { Badge() }
) {
Icon(Icons.Default.Notifications, contentDescription = null)
}
},
label = { Text("Alerts") },
selected = selectedTab == 2,
onClick = { selectedTab = 2 }
)
}
// Custom colors navigation bar
NavigationBar(
containerColor = MaterialTheme.colorScheme.surfaceVariant
) {
NavigationBarItem(
icon = { Icon(Icons.Default.Home, contentDescription = null) },
label = { Text("Home") },
selected = currentDestination == "home",
onClick = { navigateTo("home") },
colors = NavigationBarItemDefaults.colors(
selectedIconColor = MaterialTheme.colorScheme.primary,
unselectedIconColor = MaterialTheme.colorScheme.onSurfaceVariant,
selectedTextColor = MaterialTheme.colorScheme.primary,
unselectedTextColor = MaterialTheme.colorScheme.onSurfaceVariant
)
)
}Install with Tessl CLI
npx tessl i tessl/maven-org-jetbrains-compose-material--material-uikitarm64