Skip to main content
The CptTopBar is a Material3 TopAppBar component that displays the app’s branding and provides access to the navigation drawer. It serves as the primary branding element and navigation entry point at the top of the screen.

Overview

Package: es.mobiledev.commonandroid.ui.component.topBar File: commonAndroid/src/main/java/es/mobiledev/commonandroid/ui/component/topBar/CptTopBar.kt:16 A simple, stateless composable that renders the application’s top bar with consistent branding.

API Reference

CptTopBar

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun CptTopBar(
    modifier: Modifier = Modifier,
)

Parameters

modifier
Modifier
default:"Modifier"
Modifier to be applied to the TopAppBar container
This component currently has no configurable parameters beyond the modifier. Future versions may add parameters for navigation callbacks or dynamic titles.

Implementation Details

Structure

The top bar consists of two main elements:
Navigation Icon
IconButton
Left-side drawer icon button
  • Icon: R.drawable.ic_cpt_drawer
  • Action: Currently TODO - intended for opening navigation drawer
Title
Image
Center-aligned app logo
  • Image: R.drawable.topbar_cpt_title
  • Content Description: null (decorative)

Source Code

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun CptTopBar(
    modifier: Modifier = Modifier,
) {
    TopAppBar(
        title = {
            Image(
                painter = painterResource(R.drawable.topbar_cpt_title),
                contentDescription = null,
            )
        },
        navigationIcon = {
            IconButton(
                onClick = { /* TODO: Add navigation */ },
            ) {
                Icon(
                    painter = painterResource(R.drawable.ic_cpt_drawer),
                    contentDescription = null,
                )
            }
        },
        modifier = modifier,
    )
}
The component uses @OptIn(ExperimentalMaterial3Api::class) as TopAppBar is currently experimental in Material3.

Usage Examples

Basic Usage

@Composable
fun MyScreen() {
    Scaffold(
        topBar = { CptTopBar() }
    ) { paddingValues ->
        // Screen content
        Column(modifier = Modifier.padding(paddingValues)) {
            Text("Screen content")
        }
    }
}

With ScreenWrapper

@Composable
fun AppScreen() {
    ScreenWrapper(
        topBar = { CptTopBar() },
        showTopAppBar = true,
    ) { paddingValues ->
        // Content automatically padded
        Box(modifier = Modifier.padding(paddingValues)) {
            // Your UI
        }
    }
}

In Navigation Setup

@Composable
fun AppNavigation(navController: NavHostController) {
    val currentScreen by navController.currentBackStackEntryAsState()
    val showTopBar = currentScreen?.destination?.route
        ?.let { it != "launcher" } ?: false

    ScreenWrapper(
        topBar = { CptTopBar() },
        showTopAppBar = showTopBar,
        bottomBar = { /* ... */ },
    ) { paddingValues ->
        NavHost(
            navController = navController,
            startDestination = AppScreens.Home,
            modifier = Modifier.padding(paddingValues)
        ) {
            // Navigation destinations
        }
    }
}

Conditional Display

@Composable
fun DynamicTopBar(screen: AppScreens) {
    if (screen.hasTopBar) {
        CptTopBar()
    }
}

Customization

Adding Navigation Callback

To make the drawer icon functional:
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun CptTopBar(
    modifier: Modifier = Modifier,
    onNavigationClick: () -> Unit = {},  // Add callback parameter
) {
    TopAppBar(
        title = {
            Image(
                painter = painterResource(R.drawable.topbar_cpt_title),
                contentDescription = null,
            )
        },
        navigationIcon = {
            IconButton(
                onClick = onNavigationClick,  // Use callback
            ) {
                Icon(
                    painter = painterResource(R.drawable.ic_cpt_drawer),
                    contentDescription = "Open navigation drawer",
                )
            }
        },
        modifier = modifier,
    )
}
Usage with callback:
val drawerState = rememberDrawerState(DrawerValue.Closed)
val scope = rememberCoroutineScope()

ModalNavigationDrawer(
    drawerState = drawerState,
    drawerContent = { /* Drawer content */ }
) {
    Scaffold(
        topBar = {
            CptTopBar(
                onNavigationClick = {
                    scope.launch {
                        drawerState.open()
                    }
                }
            )
        }
    ) { paddingValues ->
        // Content
    }
}

Custom Colors

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun CptTopBar(
    modifier: Modifier = Modifier,
) {
    TopAppBar(
        title = { /* ... */ },
        navigationIcon = { /* ... */ },
        colors = TopAppBarDefaults.topAppBarColors(
            containerColor = MaterialTheme.colorScheme.primaryContainer,
            titleContentColor = MaterialTheme.colorScheme.onPrimaryContainer,
            navigationIconContentColor = MaterialTheme.colorScheme.onPrimaryContainer,
        ),
        modifier = modifier,
    )
}

Dynamic Title

For screens that need different titles:
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun CptTopBar(
    title: @Composable () -> Unit = {
        Image(
            painter = painterResource(R.drawable.topbar_cpt_title),
            contentDescription = null,
        )
    },
    modifier: Modifier = Modifier,
) {
    TopAppBar(
        title = title,
        navigationIcon = { /* ... */ },
        modifier = modifier,
    )
}
Usage:
CptTopBar(
    title = {
        Text(
            text = "Article Details",
            style = MaterialTheme.typography.titleLarge
        )
    }
)

Integration Points

With AppNavigation

@Composable
fun AppNavigation(navController: NavHostController) {
    var currentScreen: AppScreens by remember { 
        mutableStateOf(AppScreens.Launcher) 
    }
    val showTopAppBar by remember { 
        derivedStateOf { currentScreen.hasTopBar } 
    }

    ScreenWrapper(
        topBar = { CptTopBar() },
        showTopAppBar = showTopAppBar,  // Controlled by screen
        // ...
    )
}

With BaseScreen

@Composable
fun MyFeatureScreen() {
    BaseScreen(
        topBar = { CptTopBar() },
        isLoading = false,
    ) { paddingValues ->
        // Screen content
    }
}

Styling and Theming

Material3 Defaults

The component inherits styling from Material3’s TopAppBar:
Height
Dp
64dp (default Material3 TopAppBar height)
Container Color
Color
MaterialTheme.colorScheme.surface (default)
Content Color
Color
MaterialTheme.colorScheme.onSurface (default)
Elevation
Dp
0dp when scrolled to top, 3dp when scrolled

Elevation with Scroll Behavior

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun MyScreen() {
    val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior()

    Scaffold(
        topBar = {
            CptTopBar(
                modifier = Modifier
                    // Connect to scroll behavior for elevation
            )
        },
        modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection)
    ) { paddingValues ->
        LazyColumn(modifier = Modifier.padding(paddingValues)) {
            // Scrollable content
        }
    }
}

Accessibility

Current State

The current implementation has contentDescription = null for both the logo and drawer icon. This should be updated for better accessibility.
TopAppBar(
    title = {
        Image(
            painter = painterResource(R.drawable.topbar_cpt_title),
            contentDescription = "Compose Project Template",  // Add description
        )
    },
    navigationIcon = {
        IconButton(
            onClick = { /* TODO */ },
        ) {
            Icon(
                painter = painterResource(R.drawable.ic_cpt_drawer),
                contentDescription = "Open navigation menu",  // Add description
            )
        }
    },
)

Testing Accessibility

@Test
fun topBarAccessibility() {
    composeTestRule.setContent {
        CptTopBar()
    }

    // Verify navigation icon is accessible
    composeTestRule
        .onNodeWithContentDescription("Open navigation menu")
        .assertExists()
        .assertHasClickAction()
}

Preview Support

The component includes preview annotations:
@PreviewLightDark
@Composable
private fun Preview() {
    CptTopBar(
        modifier = Modifier,
    )
}
The @PreviewLightDark annotation generates previews for both light and dark themes automatically.

Best Practices

Keep it Simple

The top bar should remain uncluttered. Limit actions to 2-3 icons maximum.

Consistent Branding

Use the same top bar across all main screens for consistent brand identity.

Proper Padding

Always apply the padding from Scaffold to avoid content being hidden behind the bar.

Accessibility

Always provide content descriptions for icons and images.

Common Patterns

@Composable
fun StandardScreen() {
    Scaffold(
        topBar = { CptTopBar() }
    ) { paddingValues ->
        Content(Modifier.padding(paddingValues))
    }
}
Most common usage pattern.

See Also

Build docs developers (and LLMs) love