Skip to content

Navigation Bar/Rail in Jetpack Compose

The Fiori Android SDK provides a Material3-based Navigation Bar or Rail component built with the latest Jetpack Compose libraries.

Anatomy

Navigation Bar Anatomy

Basic Navigation Rail

Navigation Rail Anatomy

Navigation Rail with Profile

Navigation Rail with FAB

Development

Navigation Bar and Navigation Rail are two Composable UI components fully implemented in Jetpack Compose.

@Composable
fun FioriNavigationBar(
    data: FioriNavigationBarData,
    modifier: Modifier = Modifier,
    containerColor: Color = FioriNavigationDefaults.navigationContainerColor(),
    contentColor: Color = FioriNavigationDefaults.navigationContentColor(),
    selectedItemColor: Color = FioriNavigationDefaults.navigationSelectedItemColor(),
    indicatorColor: Color = FioriNavigationDefaults.navigationIndicatorColor()
) {
    val navigationBarItems = data.navigationItems
    val labelVisibility = data.labelVisibility
    FioriNavigationBarInternal(
        containerColor = containerColor,
        contentColor = contentColor,
        modifier = modifier
    ) {
        navigationBarItems.forEach { navBarItem ->
            FioriNavigationBarItem(
                selected = navBarItem.selected,
                onClick = navBarItem.onClick, //{ onNavigateToDestination(destination) },
                icon = if (navBarItem.selected) navBarItem.selectedIcon else navBarItem.unselectedIcon,
                hasBadge = navBarItem.hasBadge,
                badgeText = navBarItem.badgeText,
                isSmallBadge = navBarItem.isSmallBadge,
                label = navBarItem.label,
                alwaysShowLabel = (labelVisibility == LabelVisibility.LABELED),
                neverShowLabel = (labelVisibility == LabelVisibility.UNLABELED),
                colors = FioriNavigationItemDefaults.colors(
                    selectedIconColor = selectedItemColor,
                    unselectedIconColor = contentColor,
                    selectedTextColor = selectedItemColor,
                    unselectedTextColor = contentColor,
                    indicatorColor = indicatorColor
                )
            )
        }
    }
}
@Composable
fun FioriNavigationRail(
    data: FioriNavigationRailData,
    modifier: Modifier = Modifier,
    containerColor: Color = FioriNavigationDefaults.navigationContainerColor(),
    contentColor: Color = FioriNavigationDefaults.navigationContentColor(),
    selectedItemColor: Color = FioriNavigationDefaults.navigationSelectedItemColor(),
    indicatorColor: Color = FioriNavigationDefaults.navigationIndicatorColor()
) {
    val navigationItems = data.navigationItems
    val labelVisibility = data.labelVisibility
    val navigationRailHeader = data.navigationRailHeader

    var footer: @Composable (() -> Unit)? = null
    var header: @Composable (ColumnScope.() -> Unit)? = null
    if (navigationRailHeader != null) {
        when (navigationRailHeader.headerType) {
            HeaderType.PROFILE -> {
                if ( navigationRailHeader.profileAlignment == HeaderProfileAlignment.TOP) {
                    header = {
                        navigationRailHeader.profileInitial?.let {
                            FioriProfileInitial(
                                initial = it,
                            )
                        }
                    }
                } else {
                    footer = {
                        navigationRailHeader.profileInitial?.let {
                            FioriProfileInitial(
                                initial = it,
                            )
                        }
                    }
                }
            }
            HeaderType.FAB -> {
                header = navigationRailHeader.floatingActionButton
            }
            HeaderType.BOTH -> {
                header = navigationRailHeader.floatingActionButton
                footer = {
                    navigationRailHeader.profileInitial?.let {
                        FioriProfileInitial(
                            initial = it,
                        )
                    }
                }
            }
        }
    }
    FioriNavigationRailInternal(
        modifier = modifier,
        containerColor = containerColor,
        contentColor = contentColor,
        header = header,
        footer = footer,
    ) {
        navigationItems.forEach { navItem ->
            FioriNavigationRailItem(
                selected = navItem.selected,
                onClick = navItem.onClick,
                icon = if (navItem.selected) navItem.selectedIcon else navItem.unselectedIcon,
                hasBadge = navItem.hasBadge,
                badgeText = navItem.badgeText,
                isSmallBadge = navItem.isSmallBadge,
                label = navItem.label,
                alwaysShowLabel = (labelVisibility == LabelVisibility.LABELED),
                neverShowLabel = (labelVisibility == LabelVisibility.UNLABELED),
                colors = FioriNavigationItemDefaults.colors(
                    selectedIconColor = selectedItemColor,
                    unselectedIconColor = contentColor,
                    selectedTextColor = selectedItemColor,
                    unselectedTextColor = contentColor,
                    indicatorColor = indicatorColor
                )
            )
        }
    }
}

Using the Navigation Bar or Rail in an Application

Depending on the screen size of the device, a Navigation Bar or Navigation Rail will be displayed for top-level navigation destinations.

  • Compact: Displays the Navigation Bar on the bottom of the screen.
  • Medium and Large: Displays the Navigation Rail on the left side of the screen.
Navigation Bar with labels Navigation Bar with no labels

Navigation Rail with No Labels

Navigation Rail with Selected Label

How to Add a Navigation Bar and a Navigation Rail for Your Application

FioriNavigationBar is a Composable function that takes one parameter:

  • A FioriNavigationBarData that specifies the data to be displayed on the navigation bar, including the onClick event handlers.

FioriNavigationRail is also a Composable function that takes one parameter:

  • A FioriNavigationRailData that specifies the data to be displayed on the navigation rail, including the onClick event handlers.

Both FioriNavigationBar and FioriNavigationRail are incorporated into the application along with Jetpack Compose navigation. It is recommended to instantiate a Singleton state object at the app level to handle the display of a navigation bar/rail as well as the top-level navigation destinations. An example of that state class can be found inside the Fiori Compose Demo app.

@OptIn(ExperimentalAnimationApi::class)
@Composable
fun rememberNavigationState(
  numOfDestinations: Int,
  labelVisibility: LabelVisibility,
  windowSizeClass: WindowSizeClass,
  navController: NavController = rememberAnimatedNavController()
): FioriNavigationState {
  return remember(numOfDestinations, labelVisibility, navController, windowSizeClass) {
    FioriNavigationState(numOfDestinations, labelVisibility, navController, windowSizeClass)
  }
}

class FioriNavigationState(
  val numOfDestinations: Int,
  val labelVisibility: LabelVisibility,
  val navController: @RawValue NavController,
  private val windowSizeClass: @RawValue WindowSizeClass
) {

  val shouldShowBottomBar: Boolean
    get() = windowSizeClass.widthSizeClass == WindowWidthSizeClass.Compact ||
            windowSizeClass.heightSizeClass == WindowHeightSizeClass.Compact

  val shouldShowNavRail: Boolean
    get() = !shouldShowBottomBar

  var topLevelDestinations: List<TopLevelDestination> =
    generateTopLevelDestinations(numOfDestinations)

  var fioriNavigationBarData: FioriNavigationBarData =
    generateFioriNavigationBarData(numOfDestinations, labelVisibility)

  var fioriNavigationRailData: FioriNavigationRailData =
    generateFioriNavigationRailData(numOfDestinations, labelVisibility)

  /* the implementation of the methods are omitted here */
}

In the UI screens of the top-level destinations, assign FioriNavigationBar to the bottomBar of the Scaffold and add FioriNavigationRail to the content:

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun NavDemoHomeScreen(
  numOfDestinations: Int,
  navController: NavController,
  windowSizeClass: WindowSizeClass,
) {
  val labelVisibility = getLabelVisibility(numOfDestinations)
  val navigationState = rememberNavigationState(
    numOfDestinations = numOfDestinations,
    labelVisibility = labelVisibility,
    windowSizeClass = windowSizeClass,
    navController = navController,
  )
  Scaffold(
    topBar = {
      TopAppBar(
        title = {
          Text(text = "Fiori Navigation - Home")
        },
        navigationIcon = {
          IconButton(onClick = {navController.navigateUp()}) {
            Icon(Icons.Filled.ArrowBack, "backIcon")
          }
        }
      )
    },
    bottomBar = {
      if (navigationState.shouldShowBottomBar) {
        FioriNavigationBar(
          navigationState.fioriNavigationBarData
        )
      }
    },
    content = { it ->
      Row(
        Modifier
          .fillMaxSize()
          .padding(it)
          .windowInsetsPadding(
            WindowInsets.safeDrawing.only(
              WindowInsetsSides.Horizontal
            )
          )
      ) {
        if (navigationState.shouldShowNavRail) {
          FioriNavigationRail(
            data = navigationState.fioriNavigationRailData
          )
        }

        Column(
          modifier = Modifier
            .fillMaxSize()
            .background(MaterialTheme.colorScheme.background),
          horizontalAlignment = Alignment.CenterHorizontally
        ) {
          Text(
            text = "This is the Home page.",
            modifier = Modifier.padding(vertical = 16.dp)
          )
        }
      }
    })

}

Configuring the Navigation Bar and Navigation Rail

Use FioriNavigationBarData and FioriNavigationRailData to configure the navigation bar and rail for your application. These two data classes are very similar to each other, except that FioriNavigationRailData also has an optional FioriNavigationRailHeader for an optional profile icon and/or FAB (floating action bar) button. There are three kinds of visibility to choose from for the labels:

  • Labeled: Always show labels.
  • Selected: Only show the label on the selected item.
  • Unlabeled: Never show labels.
data class FioriNavigationBarData(
  val navigationItems: List<FioriNavigationItemData>,
  val labelVisibility: LabelVisibility = LabelVisibility.LABELED
)
data class FioriNavigationRailData(
  val navigationItems: List<FioriNavigationItemData>,
  val navigationRailHeader: FioriNavigationRailHeader? = null,
  val labelVisibility: LabelVisibility = LabelVisibility.SELECTED
)

Use FioriNavigationItemData to specify each item inside the Navigation Bar and Rail, including the icons, the label, the onClick event handlers, the selected state, and additional information about displaying the Badge.

data class FioriNavigationItemData(
  val selectedIcon: FioriIcon,
  val unselectedIcon: FioriIcon,
  val label: String,
  var onClick: () -> Unit,
  var selected: Boolean = false,
  val hasBadge: Boolean = false,
  val badgeText: String? = null,
  val isSmallBadge: Boolean = false
)

An example of creating a FioriNavigationItemData:

val navigationItemHome = FioriNavigationItemData(
    selectedIcon = FioriIcon(
        iconType = IconType.IMAGEVECTOR,
        imageVector = FioriIcons.Home_Selected,
        contentDescription = "Home Selected"
    ),
    unselectedIcon = FioriIcon(
        iconType = IconType.IMAGEVECTOR,
        imageVector = FioriIcons.Home_Unselected,
        contentDescription = "Home Unselected"
    ),
    label = "Home",
    onClick = {navigate(topLevelDestinations[0], topLevelDestinations[0].route, numOfDestinations)},
    selected = true
)

How to Add a Badge to a Navigation Bar/Rail Item

A badge represents dynamic information, such as a number for pending notifications. A badge may display a number or may only be a dot icon (a so-called "Small Badge"). To add a badge to a navigation item, simply set the following values in the FioriNavigationItemData data class:

  • hasBadge: Set it to true. The default is false.
  • isSmallBadge : Set it to _true if only displaying an icon, otherwise the default is false, to display a number.
  • badgeText: The number to be displayed on the badge, such as 8.
val navigationItemChat = FioriNavigationItemData(
    selectedIcon = FioriIcon(
        iconType = IconType.IMAGEVECTOR,
        imageVector = FioriIcons.Chat_Selected,
        contentDescription = "Chat Selected"
    ),
    unselectedIcon = FioriIcon(
        iconType = IconType.IMAGEVECTOR,
        imageVector = FioriIcons.Chat_Unselected,
        contentDescription = "Chat Unselected"
    ),
    label = "Chat",
    onClick = {},
    hasBadge = true,
    badgeText = "8",
    isSmallBadge = false
)

See the resulting badge on top of the Chat icon in the following navigation bar.

Chat Badge

Customization

There are two ways that you can customize the colors of the Navigation Bar and Navigation Rail.

  • Using a custom theme.
  • By providing custom colors to the Composable function of FioriNavigationBar() or FioriNavigationRail()

Using a Custom Theme to Customize the Navigation Bar and Navigation Rail

Instead of using FioriHorizonTheme, use FioriCustomTheme when creating the component. For example, in the CustomThemeNavBarPreview(), we read in a custom theme with a different set of theme colors and then apply FioirCustomTheme:

@Preview("Custom Theme Nav Bar")
@Composable
private fun CustomThemeNavBarPreview() {
    // Read in the custom colors from the `JSON` file in `assets/ folder`.
    val customColors = parseCustomColorsFromAssets(LocalContext.current, "custom-theme.json")

    if (customColors != null) {
        FioriCustomTheme(
            customColors = customColors,
            customLogo = null
        ) {
            val data = remember { generateNavigationBarData(3) }
            FioriNavigationBar(data = data)
        }
    }
}

It displays the Navigation Bar as follows:

Customization via Custom Theme

Setting the Color Parameters in FioriNavigationBar and FioriNavigationRail

The label and icon colors on a Navigation Bar and Navigation Rail can be customized, as well as the background color of these two UI components.

As you can see in the parameter list of the Composable function of FioriNavigationBar() or FioriNavigationRail(), there are four Color parameters:

  • containerColor: Defines the background color of the Navigation Bar or Rail.
  • contentColor: Defines the label and icon colors for unselected items.
  • selectedItemColor: Defines the label and icon colors for the selected item.
  • indicatorColor: Defines the background color of the active indicator on the selected item.

The following example shows how to set the parameters for those colors in FioriNavigationRail() in order to customize the look:

@Preview("Custom Colors Nav Rail")
@Composable
private fun CustomColorsNavRailPreview(){
    FioriHorizonTheme(darkTheme = false) {
        val data = remember {generateFioriNavigationRailData(5, LabelVisibility.LABELED) }
        FioriNavigationRail(
            data = data,
            containerColor = Color.Cyan,
            contentColor = Color.LightGray,
            selectedItemColor = Color.Red,
            indicatorColor = Color(android.graphics.Color.parseColor("#f7d8f6"))
        )
    }
}

It displays the Navigation Rail as follows:

Customization via Setting Colors

Further Reading

Here are additional resources on the Material3 Navigation Bar/Rail.

-Navigation bar -Navigation rail


Last update: February 20, 2023