Top Bar
The bar at the top of a screen: the screen's title, with a navigation icon before it and actions after it.
import com.pantrixui.components.container.topbar.PantrixTopBar
- Title and subtitle.
titleis the screen's name, on one line;subtitleis written under it, smaller: a count, a state. Either is cut short when it does not fit the room between the navigation icon and the actions, and does not run under them. Their text styles are given withtitleStyleandsubtitleStyle; unless given, they are the theme'stitleandfootnotestyles. - Platform. The title stands at the start on Android, desktop and the web, and in the middle of the bar on iOS. A title in the middle is in the exact middle of the bar while it fits there; when it does not, it moves away from the wider side. The bar is 54dp tall on Android and iOS, 56dp on desktop and the web.
- Navigation icon and actions.
navigationIconstands before the title: a back button, or a button that opens a menu.actionsis a row after the title: icon buttons for what the screen can do, the most used first. - Back button.
PantrixBackButtondraws the platform's icon: an arrow on Android, desktop and the web, a chevron on iOS; both turn round in a right-to-left layout. A screen reader calls it "Back" in the theme's language. - Line over scrolled content. While
scrolledis true a line is drawn along the bar's bottom edge. The line is inside the bar's height; nothing moves as it comes and goes. - Collapsing title. A bar given a
statewrites its title large while it is open, in a band under the row; as the content scrolls up the band closes and the title comes into the row. The content hands the bar its scroll withModifier.topBarScroll(state). - Insets.
insetsis the room the bar keeps clear at its edges: for the status bar and a cutout. The bar's fill runs behind them. - Colour. The bar is the theme's
surface, with the title intextPrimaryand the subtitle intextSecondary.TopBarColorsgives the fill, the colour of the content and the line; on a bar with a fill, all of it is written in one colour. - Reading order. A screen reader and the Tab key go through the bar in the order it reads: the navigation icon, the title and the subtitle under it, the actions. The title is the screen's heading for a screen reader.
- Automation. With a
tagKeythe bar is taggedTopBar_<tagKey>, and the large title of a collapsing barTopBar_<tagKey>.Title_large. The back button is an icon button: it carries the tagIconButton_<tagKey>, and its press is reported as an icon button's.
Examples
Title only
The least there is to write: title alone. With no navigation icon, a title at the start begins at the bar's own inset; on iOS the title is in the middle.
PantrixSurface(modifier = Modifier.fillMaxWidth(), border = PantrixTheme.colors.divider) {
PantrixTopBar(title = "Inbox", tagKey = "plain")
}
Back button, actions and content scrolling under the bar
A conversation screen: a back button, two actions and a list that scrolls under the bar. navigationIcon puts a PantrixBackButton before the title; the subtitle says how many times the back button has been pressed. actions puts two icon buttons after the title: drawn as they are, in the brand's colour.
Scroll the list: as soon as it leaves its top, a line appears along the bar's bottom edge (scrolled), and it stays for as long as the list can scroll back. The line is inside the bar's height; nothing moves as it comes and goes.
val list = rememberLazyListState()
var back by rememberSaveable { mutableIntStateOf(0) }
PantrixSurface(modifier = Modifier.fillMaxWidth(), border = PantrixTheme.colors.divider) {
Column(modifier = Modifier.height(220.dp)) {
PantrixTopBar(
title = "Conversation",
subtitle = if (back == 0) null else "Back presses: $back",
navigationIcon = { PantrixBackButton(onClick = { back++ }, tagKey = "back") },
actions = {
PantrixIconButton(
imageVector = PantrixIcons.Search,
contentDescription = "Search",
onClick = {},
tagKey = "search"
)
PantrixIconButton(
imageVector = PantrixIcons.MoreHorizontal,
contentDescription = "More",
onClick = {},
tagKey = "more"
)
},
scrolled = list.canScrollBackward,
tagKey = "conversation"
)
LazyColumn(
// The list is as wide as the bar: left narrower, a finger on the space beside it would scroll
// the page, not the list.
modifier = Modifier.fillMaxWidth(),
state = list,
contentPadding = PaddingValues(PantrixTheme.spacing.lg),
verticalArrangement = Arrangement.spacedBy(PantrixTheme.spacing.md)
) {
items(12) { index -> PantrixText(text = "Message ${index + 1}") }
}
}
}
Subtitle
subtitle puts a line of smaller text under the title: a count, a state. The navigation icon here is not a back button but an icon button that opens a menu.
PantrixSurface(modifier = Modifier.fillMaxWidth(), border = PantrixTheme.colors.divider) {
PantrixTopBar(
title = "Projects",
subtitle = "12 open",
navigationIcon = {
PantrixIconButton(
imageVector = PantrixIcons.Menu,
contentDescription = "Open menu",
onClick = {},
tagKey = "menu"
)
},
tagKey = "subtitle"
)
}
Title and subtitle in text styles of their own
The title and the subtitle can each be given a text style: titleStyle and subtitleStyle. Unless given, they are the theme's title and footnote styles; here callout and caption are given. Where the style has no colour of its own, the text stays in the bar's colour. A larger style makes the bar as tall as its line needs.
PantrixSurface(modifier = Modifier.fillMaxWidth(), border = PantrixTheme.colors.divider) {
PantrixTopBar(
title = "Projects",
subtitle = "12 open",
titleStyle = PantrixTheme.typography.callout,
subtitleStyle = PantrixTheme.typography.caption,
navigationIcon = {
PantrixIconButton(
imageVector = PantrixIcons.Menu,
contentDescription = "Open menu",
onClick = {},
tagKey = "type_menu"
)
},
tagKey = "own_type"
)
}
A title that does not fit is cut short
The title does not run under the navigation icon or the actions: a title too long for the room left between them is cut short. The bar here is at most 360dp wide; the title does not fit between the back button and the star, and its end is cut off.
PantrixSurface(modifier = Modifier.widthIn(max = 360.dp), border = PantrixTheme.colors.divider) {
PantrixTopBar(
title = "Third-quarter reports, as they were sent to the board of directors",
navigationIcon = { PantrixBackButton(onClick = {}, tagKey = "long_back") },
actions = {
PantrixIconButton(
imageVector = PantrixIcons.Star,
contentDescription = "Add to favourites",
onClick = {},
tagKey = "favourite"
)
},
tagKey = "long"
)
}
Where the title stands: at the start and in the middle
Where the title stands follows the platform: at the start on Android, desktop and the web, in the middle of the bar on iOS. Here the same bar is drawn twice, and the place is given with style: TopBarStyle(titleAlignment = ...). Both show, whichever platform you are on.
A title in the middle is in the middle of the bar whatever stands beside it: the back button does not push it aside.
PantrixSurface(modifier = Modifier.fillMaxWidth(), border = PantrixTheme.colors.divider) {
PantrixTopBar(
title = "Settings",
navigationIcon = { PantrixBackButton(onClick = {}, tagKey = "start_back") },
style = TopBarStyle(titleAlignment = TopBarTitleAlignment.Start),
tagKey = "start"
)
}
PantrixSurface(modifier = Modifier.fillMaxWidth(), border = PantrixTheme.colors.divider) {
PantrixTopBar(
title = "Settings",
navigationIcon = { PantrixBackButton(onClick = {}, tagKey = "center_back") },
style = TopBarStyle(titleAlignment = TopBarTitleAlignment.Center),
tagKey = "center"
)
}
The back button's icon
PantrixBackButton takes the icon it draws from the bar's style: TopBarStyle.backIcon. The platform's own icon is an arrow on Android, desktop and the web, a chevron on iOS. Here each of the two bars is given an icon with style; a back button in a bar's navigationIcon uses the style that bar was given.
Both turn round in a right-to-left layout: flip the reading direction from the "Typeface and direction" menu in the app's top bar and look.
PantrixSurface(modifier = Modifier.fillMaxWidth(), border = PantrixTheme.colors.divider) {
PantrixTopBar(
title = "Arrow",
navigationIcon = { PantrixBackButton(onClick = {}, tagKey = "arrow_back") },
style = TopBarStyle(backIcon = BackButtonIcon.Arrow),
tagKey = "arrow"
)
}
PantrixSurface(modifier = Modifier.fillMaxWidth(), border = PantrixTheme.colors.divider) {
PantrixTopBar(
title = "Chevron",
navigationIcon = { PantrixBackButton(onClick = {}, tagKey = "chevron_back") },
style = TopBarStyle(backIcon = BackButtonIcon.Chevron),
tagKey = "chevron"
)
}
With nowhere to go back to
On the first screen of a flow there is nowhere to go back to: the enabled of PantrixBackButton is false and the button cannot be pressed. Move on with the button at the end: the back button is enabled from the second step on.
var step by rememberSaveable { mutableIntStateOf(0) }
PantrixSurface(modifier = Modifier.fillMaxWidth(), border = PantrixTheme.colors.divider) {
PantrixTopBar(
title = "Step ${step + 1} / 3",
navigationIcon = { PantrixBackButton(onClick = { step -= 1 }, enabled = step > 0, tagKey = "step_back") },
actions = {
PantrixIconButton(
imageVector = PantrixIcons.ArrowForward,
contentDescription = "Next step",
onClick = { step += 1 },
enabled = step < 2,
tagKey = "step_next"
)
},
tagKey = "steps"
)
}
A large title that collapses as the list scrolls
A collapsing title: the state is the app's and is kept with rememberPantrixTopBarState(); the list hands the bar its scroll with Modifier.topBarScroll(state). While the bar is open the title is written large, in the theme's headline style, in a band under the row. The large title stands at the bar's start on every platform; the title in the row stands where the platform has it, in the middle on iOS.
Scroll the list up: first the band closes and the title comes into the row, and only then does the list scroll. The bar opens again only once the list is back at its top. Left between its two heights by a finger, it goes on to the nearer; after a mouse wheel it goes on the way the wheel was last turned. A screen reader reads the title in the row: that one is there all the time, and the title is not read twice.
The bar and the list are inside a scaffold, as they would be in an app: the list fills the whole scaffold and is given the bar's height as its top inset. As the bar closes and opens the list itself stays where it is; only its inset changes. The button below takes the list back to its top and opens the bar by setting collapsedFraction to zero.
val topBar = rememberPantrixTopBarState()
val list = rememberLazyListState()
val scope = rememberCoroutineScope()
val spacing = PantrixTheme.spacing
PantrixSurface(modifier = Modifier.fillMaxWidth(), border = PantrixTheme.colors.divider) {
PantrixScaffold(
modifier = Modifier.height(320.dp),
topBar = { insets ->
PantrixTopBar(
title = "Inbox",
subtitle = "3 unread",
navigationIcon = { PantrixBackButton(onClick = {}, tagKey = "collapsing_back") },
actions = {
PantrixIconButton(
imageVector = PantrixIcons.Search,
contentDescription = "Search",
onClick = {},
tagKey = "collapsing_search"
)
},
scrolled = list.canScrollBackward,
state = topBar,
insets = insets,
tagKey = "collapsing"
)
},
containerColor = PantrixTheme.colors.surface,
// A box inside the page: the system bars are in the page's scaffold, and this one has no insets.
windowInsets = WindowInsets(0, 0, 0, 0)
) { padding ->
LazyColumn(
modifier = Modifier.fillMaxSize().topBarScroll(topBar),
state = list,
contentPadding = PaddingValues(
start = spacing.lg,
top = padding.calculateTopPadding() + spacing.lg,
end = spacing.lg,
bottom = spacing.lg
),
verticalArrangement = Arrangement.spacedBy(spacing.md)
) {
items(24) { index -> PantrixText(text = "Conversation ${index + 1}") }
}
}
}
PantrixButton(
text = "Back to top and open the bar",
onClick = {
scope.launch {
list.scrollToItem(0)
topBar.collapsedFraction = 0f
}
},
variant = ButtonVariant.Outline,
size = ButtonSize.Small,
tagKey = "collapsing_to_top"
)
The insets the bar keeps clear
insets is the room the bar keeps clear at its edges: room for the status bar and a cutout. A PantrixScaffold hands it to its top bar itself; here a top inset of 28dp is given by hand. The bar's fill runs behind the inset, and the title and the back button stay below it: the bar is as tall as its style's height plus the inset.
PantrixSurface(modifier = Modifier.fillMaxWidth(), border = PantrixTheme.colors.divider) {
PantrixTopBar(
title = "Settings",
navigationIcon = { PantrixBackButton(onClick = {}, tagKey = "insets_back") },
colors = TopBarColors(container = PantrixTheme.colors.primary.container),
insets = PaddingValues(top = 28.dp),
tagKey = "insets"
)
}
In its own colours
colors gives the bar colours of its own in place of the theme's. The first bar's fill is the colour of one of the theme's roles, and that alone is given: the theme pairs the colour of the title, the subtitle and the back button with it.
The second bar is in a colour the theme does not have, the app's own purple: the colour that reads on it is given with content. The title, the subtitle and the back button take the content colour. The icon button has colours of its own: the colour that reads on the fill is given to it separately.
val own = ownColors()
PantrixSurface(modifier = Modifier.fillMaxWidth(), border = PantrixTheme.colors.divider) {
PantrixTopBar(
title = "Inbox",
subtitle = "Only the fill is given",
navigationIcon = { PantrixBackButton(onClick = {}, tagKey = "role_back") },
colors = TopBarColors(container = PantrixTheme.colors.primary.color),
tagKey = "role"
)
}
PantrixSurface(modifier = Modifier.fillMaxWidth(), border = PantrixTheme.colors.divider) {
PantrixTopBar(
title = "Inbox",
subtitle = "3 unread",
navigationIcon = { PantrixBackButton(onClick = {}, tagKey = "own_back") },
actions = {
PantrixIconButton(
imageVector = PantrixIcons.Search,
contentDescription = "Search",
onClick = {},
colors = ButtonColors(container = Color.Transparent, content = own.onColor),
tagKey = "own_search"
)
},
colors = TopBarColors(container = own.color, content = own.onColor),
tagKey = "own"
)
}
API
title
The bar at the top of a screen: the screen's title, with a navigation icon before it and actions after it.
| Parameter | Type | Default | What it is |
|---|---|---|---|
title | String | the screen's name, on one line. | |
modifier | Modifier | Modifier | the bar's modifier. |
subtitle | String? | null | a line under the title in a smaller type, a count or a state, or null for none. |
titleStyle | TextStyle | PantrixTheme.typography.title | the type the title is set in, in the row: the theme's title unless given. A larger one makes the bar as tall as its line needs. Its colour is the bar's unless the style has one of its own. The large title of a collapsing bar is the theme's headline, whatever this is. |
subtitleStyle | TextStyle | PantrixTheme.typography.footnote | the type the subtitle is set in, in the row and in a collapsing bar's band: the theme's footnote unless given. Its colour is the bar's unless the style has one of its own. |
navigationIcon | (@Composable () -> Unit)? | null | what stands before the title: a PantrixBackButton, or a button that opens a drawer. Null for none, and the title then starts at the bar's own inset. |
actions | (@Composable RowScope.() -> Unit)? | null | what stands after the title, in a row: icon buttons for what the screen can do, the most used first. A button draws in its own colours, the brand's unless it is given others: on a bar with a fill of its own, give each the colour that reads there. |
scrolled | Boolean | false | whether a line is drawn along the bar's bottom edge, to part it from what is scrolled under it: pass whether the content can scroll back, as listState.canScrollBackward. The line is inside the bar's height, and nothing moves as it comes and goes. |
state | PantrixTopBarState? | null | gives the bar a collapsing title: kept with rememberPantrixTopBarState, and handed the content's scroll with Modifier.topBarScroll. Null is a bar of one height. |
style | TopBarStyle? | null | this bar's own geometry, which a PantrixBackButton in navigationIcon draws with too. Only the fields it sets change, on every platform. |
colors | TopBarColors? | null | this bar's own colours, or null for the theme's: its surface, with the title in textPrimary and the subtitle in textSecondary. |
insets | PaddingValues | PaddingValues() | the room the bar keeps clear at its edges, the status bar's and a cutout's, as a PantrixScaffold hands its top bar. The bar's fill runs behind them. |
tagKey | String? | null | the bar's automation key, the same in every language: the bar is tagged TopBar_<tagKey>, and the large title of a collapsing one TopBar_<tagKey>.Title_large. |
TopBarStyle
PantrixTopBar's geometry, and the icon PantrixBackButton draws.
| Parameter | Type | Default | What it is |
|---|---|---|---|
height | Dp | Dp.Unspecified | How tall the bar is, under the insets it keeps clear: 54dp on Android and iOS, 56dp on desktop and the web. |
expandedHeight | Dp | Dp.Unspecified | How tall a bar with a collapsing title is before it collapses: height, and under it the band that holds the title in a large type. 106dp on Android and iOS, 104dp on desktop and the web: a band of 52dp, and of 48dp on desktop and the web. A band is taller only where its lines need it, and a style that sets this no higher than height has no band: its bar is of one height, with a state or without. |
horizontalPadding | Dp | Dp.Unspecified | The room between the bar's edges and the navigation icon and the actions: 8dp. |
titleInset | Dp | Dp.Unspecified | How far the title stands from the bar's start edge where there is no navigation icon, and from its end edge where there are no actions: 16dp. The title is never nearer an edge than horizontalPadding. |
titleGap | Dp | Dp.Unspecified | The room between the navigation icon and the title, and between the title and the actions: 8dp. |
titleAlignment | TopBarTitleAlignment? | null | Where the title stands: in the middle on iOS, at the start elsewhere. |
backIcon | BackButtonIcon? | null | What a PantrixBackButton draws: a chevron on iOS, an arrow elsewhere. |
TopBarColors
What a PantrixTopBar is painted with, for a bar that does not take the theme's colours: a colour that is set is used, and one that is left out stays the theme's.
| Parameter | Type | Default | What it is |
|---|---|---|---|
container | Color | Color.Unspecified | The bar's fill, which runs behind the insets the bar keeps clear. The theme's surface. |
content | Color | Color.Unspecified | The title, the subtitle, and the icons of the navigation icon and the actions that give no colour of their own. On the theme's own fill the title is the theme's textPrimary, the subtitle its textSecondary, and an icon its own default. |
divider | Color | Color.Unspecified | The line along the bar's bottom edge while content is scrolled under it. The theme's divider on the theme's own grounds. On a fill, where that line would not read, a share of content: the edge a subtle button has. |