Use toolbars to anchor a screen title, navigation affordances, and top-level actions.
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.composables.icons.lucide.ArrowLeft
import com.composables.icons.lucide.EllipsisVertical
import com.composables.icons.lucide.Lucide
import com.composables.icons.lucide.Share
import com.composables.ui.components.ButtonStyle
import com.composables.ui.components.Icon
import com.composables.ui.components.IconButton
import com.composables.ui.components.Text
import com.composables.ui.components.Toolbar
@Composable
fun ToolbarWithActionsExample() {
Toolbar(
modifier = Modifier.fillMaxWidth(),
title = { Text("Title") },
leading = {
IconButton(
onClick = { /* TODO */ },
style = ButtonStyle.Ghost,
) {
Icon(Lucide.ArrowLeft, contentDescription = "Go back")
}
},
trailing = {
IconButton(
onClick = { /* TODO */ },
style = ButtonStyle.Ghost,
) {
Icon(
imageVector = Lucide.Share,
contentDescription = "Share",
modifier = Modifier.size(18.dp),
)
}
IconButton(
onClick = { /* TODO */ },
style = ButtonStyle.Ghost,
) {
Icon(
imageVector = Lucide.EllipsisVertical,
contentDescription = "More options",
modifier = Modifier.size(18.dp),
)
}
},
)
}Installation
implementation("com.composables:ui:0.1.0")Add the required dependencies
implementation("com.composables:composeunstyled:2.7.0")
Copy and paste the following sources into your project
components/Toolbar.kt
package com.composables.ui.components
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxScope
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.WindowInsetsSides
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.only
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.systemBars
import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.composeunstyled.LocalContentColor
import com.composeunstyled.LocalTextStyle
import com.composeunstyled.ProvideContentColor
import com.composeunstyled.ProvideTextStyle
import com.composeunstyled.buildModifier
import kotlin.jvm.JvmInline
/**
* Size variants for the standard Toolbar.
*/
@JvmInline
value class ToolbarSize internal constructor(@Suppress("unused") private val value: Int) {
companion object {
/**
* Uses the standard single-row toolbar height.
*/
val Medium = ToolbarSize(0)
/**
* Uses the large title toolbar layout.
*/
val Large = ToolbarSize(1)
}
}
/**
* A top app bar with medium and large title variants.
* @param title Composable title content displayed by the toolbar.
* @param modifier Modifier applied to the toolbar.
* @param backgroundColor Background color used by the toolbar.
* @param contentColor Color used for toolbar content.
* @param windowInsets Insets applied around the toolbar content.
* @param leading Optional leading content shown before the title.
* @param trailing Optional trailing content shown after the title.
* @param size Size variant used by the standard toolbar.
*/
@Composable
fun Toolbar(
title: @Composable RowScope.() -> Unit,
modifier: Modifier = Modifier,
backgroundColor: Color = Color.Transparent,
contentColor: Color = LocalContentColor.current,
windowInsets: WindowInsets = toolbarWindowInsets(),
leading: @Composable (RowScope.() -> Unit)? = null,
trailing: @Composable (RowScope.() -> Unit)? = null,
size: ToolbarSize = ToolbarSize.Medium,
) {
if (size == ToolbarSize.Large) {
LargeTitleToolbar(
modifier = modifier,
backgroundColor = backgroundColor,
contentColor = contentColor,
windowInsets = windowInsets,
title = title,
size = size,
leading = leading,
trailing = trailing,
)
} else {
MediumTitleToolbar(
modifier = modifier,
backgroundColor = backgroundColor,
contentColor = contentColor,
windowInsets = windowInsets,
title = title,
size = size,
leading = leading,
trailing = trailing,
)
}
}
@Composable
private fun MediumTitleToolbar(
modifier: Modifier,
backgroundColor: Color,
contentColor: Color,
windowInsets: WindowInsets,
title: @Composable RowScope.() -> Unit,
size: ToolbarSize,
leading: @Composable (RowScope.() -> Unit)?,
trailing: @Composable (RowScope.() -> Unit)?,
) {
ToolbarContainer(
modifier = modifier,
backgroundColor = backgroundColor,
contentColor = contentColor,
windowInsets = windowInsets,
height = toolbarHeightFor(size),
) {
Row(
modifier = Modifier.align(Alignment.CenterStart),
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalAlignment = Alignment.CenterVertically,
) {
if (leading != null) {
leading()
}
ProvideTextStyle(LocalTextStyle.current.merge(ToolbarTitleTextStyle)) {
title()
}
}
if (trailing != null) {
Row(
modifier = Modifier.align(Alignment.CenterEnd),
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalAlignment = Alignment.CenterVertically,
content = trailing,
)
}
}
}
@Composable
private fun LargeTitleToolbar(
modifier: Modifier,
backgroundColor: Color,
contentColor: Color,
windowInsets: WindowInsets,
title: @Composable RowScope.() -> Unit,
size: ToolbarSize,
leading: @Composable (RowScope.() -> Unit)?,
trailing: @Composable (RowScope.() -> Unit)?,
) {
ToolbarContainer(
modifier = modifier,
backgroundColor = backgroundColor,
contentColor = contentColor,
windowInsets = windowInsets,
height = toolbarHeightFor(size),
) {
if (leading != null) {
Row(
modifier = Modifier
.height(64.dp)
.align(Alignment.TopStart),
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalAlignment = Alignment.CenterVertically,
content = leading,
)
}
if (trailing != null) {
Row(
modifier = Modifier
.height(64.dp)
.align(Alignment.TopEnd),
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalAlignment = Alignment.CenterVertically,
content = trailing,
)
}
Row(
modifier = Modifier
.align(Alignment.BottomStart)
.padding(bottom = 12.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalAlignment = Alignment.CenterVertically,
) {
ProvideTextStyle(LocalTextStyle.current.merge(ToolbarHeaderTextStyle)) {
title()
}
}
}
}
/**
* A toolbar with centered title content.
* @param title Composable title content displayed by the toolbar.
* @param modifier Modifier applied to the toolbar.
* @param leading Optional leading content shown before the title.
* @param trailing Optional trailing content shown after the title.
* @param backgroundColor Background color used by the toolbar.
* @param contentColor Color used for toolbar content.
* @param windowInsets Insets applied around the toolbar content.
*/
@Composable
fun CenteredToolbar(
title: @Composable (RowScope.() -> Unit),
modifier: Modifier = Modifier,
leading: @Composable (RowScope.() -> Unit)? = null,
trailing: @Composable (RowScope.() -> Unit)? = null,
backgroundColor: Color = Color.Transparent,
contentColor: Color = LocalContentColor.current,
windowInsets: WindowInsets = toolbarWindowInsets(),
) {
ToolbarContainer(
modifier = modifier,
backgroundColor = backgroundColor,
height = 64.dp,
contentColor = contentColor,
windowInsets = windowInsets,
) {
if (leading != null) {
Row(
modifier = Modifier.align(Alignment.CenterStart),
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalAlignment = Alignment.CenterVertically,
) {
ProvideTextStyle(LocalTextStyle.current.merge(ToolbarTitleTextStyle)) {
leading()
}
}
}
Row(
modifier = Modifier.align(Alignment.Center),
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalAlignment = Alignment.CenterVertically,
) {
ProvideTextStyle(LocalTextStyle.current.merge(ToolbarTitleTextStyle)) {
title()
}
}
if (trailing != null) {
Row(
modifier = Modifier.align(Alignment.CenterEnd),
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalAlignment = Alignment.CenterVertically,
content = trailing,
)
}
}
}
private fun toolbarHeightFor(size: ToolbarSize): Dp = when (size) {
ToolbarSize.Large -> 112.dp
else -> 64.dp
}
private val ToolbarTitleTextStyle = TextStyle(
fontSize = 20.sp,
lineHeight = 24.sp,
fontWeight = FontWeight.Medium,
)
private val ToolbarHeaderTextStyle = TextStyle(
fontSize = 26.sp,
lineHeight = 24.sp,
fontWeight = FontWeight.SemiBold,
)
@Composable
private fun ToolbarContainer(
backgroundColor: Color,
height: Dp,
windowInsets: WindowInsets,
modifier: Modifier = Modifier,
contentColor: Color = LocalContentColor.current,
content: @Composable BoxScope.() -> Unit,
) {
ProvideContentColor(contentColor) {
Box(
modifier = modifier
.then(buildModifier {
if (backgroundColor != Color.Transparent) {
add(Modifier.background(backgroundColor))
}
})
.windowInsetsPadding(windowInsets)
.height(height)
.padding(ToolbarContentPadding),
content = content,
)
}
}
private val ToolbarContentPadding = PaddingValues(horizontal = 12.dp)
@Composable
private fun toolbarWindowInsets(): WindowInsets {
return WindowInsets.systemBars.only(
WindowInsetsSides.Horizontal + WindowInsetsSides.Top,
)
}components/Utils.kt
package com.composables.ui.components
import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.animateDpAsState
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.spring
import androidx.compose.animation.core.tween
import androidx.compose.foundation.interaction.InteractionSource
import androidx.compose.foundation.interaction.collectIsFocusedAsState
import androidx.compose.foundation.interaction.collectIsPressedAsState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.composables.ui.theme.colors
import com.composables.ui.theme.ringColor
import com.composeunstyled.FocusRingVisibility
import com.composeunstyled.collectIsFocusVisibleAsState
import com.composeunstyled.outline
import com.composeunstyled.theme.Theme
@Composable
fun Modifier.focusRing(
interactionSource: InteractionSource,
width: Dp = 2.dp,
color: Color = Theme[colors][ringColor],
shape: Shape = RectangleShape,
offset: Dp = 0.dp,
visibility: FocusRingVisibility = FocusRingVisibility.FocusVisible,
): Modifier {
val showFocusRing by if (visibility == FocusRingVisibility.FocusVisible) {
interactionSource.collectIsFocusVisibleAsState()
} else {
interactionSource.collectIsFocusedAsState()
}
val animatedWidth by animateDpAsState(
targetValue = if (showFocusRing) width else 0.dp,
animationSpec = tween(durationMillis = 120),
label = "FocusRingWidth",
)
return this then Modifier.outline(
width = animatedWidth,
color = color,
shape = shape,
offset = offset,
)
}
@Composable
fun Modifier.bouncyPress(
interactionSource: InteractionSource,
enabled: Boolean = true,
pressedScale: Float = 0.98f,
): Modifier {
val pressed by interactionSource.collectIsPressedAsState()
val scale by animateFloatAsState(
targetValue = if (enabled && pressed) pressedScale else 1f,
animationSpec = spring(
dampingRatio = Spring.DampingRatioMediumBouncy,
stiffness = Spring.StiffnessMediumLow,
),
label = "BouncyPressScale",
)
return this then Modifier.graphicsLayer {
scaleX = scale
scaleY = scale
}
}Examples
Centered
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.composables.icons.lucide.ArrowLeft
import com.composables.icons.lucide.EllipsisVertical
import com.composables.icons.lucide.Lucide
import com.composables.ui.components.ButtonStyle
import com.composables.ui.components.Icon
import com.composables.ui.components.IconButton
import com.composables.ui.components.Text
import com.composables.ui.components.CenteredToolbar
@Composable
fun CenteredToolbarExample() {
CenteredToolbar(
modifier = Modifier.fillMaxWidth(),
leading = {
IconButton(
onClick = { /* TODO */ },
style = ButtonStyle.Ghost,
) {
Icon(Lucide.ArrowLeft, contentDescription = "Go back")
}
},
title = { Text("Centered") },
trailing = {
IconButton(
onClick = { /* TODO */ },
style = ButtonStyle.Ghost,
) {
Icon(
imageVector = Lucide.EllipsisVertical,
contentDescription = "More options",
modifier = Modifier.size(18.dp),
)
}
},
)
}Large
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.composables.icons.lucide.EllipsisVertical
import com.composables.icons.lucide.Lucide
import com.composables.ui.components.ButtonStyle
import com.composables.ui.components.Icon
import com.composables.ui.components.IconButton
import com.composables.ui.components.Text
import com.composables.ui.components.Toolbar
import com.composables.ui.components.ToolbarSize
@Composable
fun LargeToolbarExample() {
Toolbar(
modifier = Modifier.fillMaxWidth(),
title = { Text("Large title") },
size = ToolbarSize.Large,
trailing = {
IconButton(
onClick = { /* TODO */ },
style = ButtonStyle.Ghost,
) {
Icon(
imageVector = Lucide.EllipsisVertical,
contentDescription = "More options",
modifier = Modifier.size(18.dp),
)
}
},
)
}API Reference
Toolbar
A top app bar with medium and large title variants.
@Composable
fun Toolbar(
title: @Composable RowScope.() -> Unit,
modifier: Modifier = Modifier,
backgroundColor: Color = Color.Transparent,
contentColor: Color = LocalContentColor.current,
windowInsets: WindowInsets = toolbarWindowInsets(),
leading: @Composable (RowScope.() -> Unit)? = null,
trailing: @Composable (RowScope.() -> Unit)? = null,
size: ToolbarSize = ToolbarSize.Medium,
)
| Parameter | Type | Description |
|---|---|---|
title |
@Composable RowScope.() -> Unit |
Composable title content displayed by the toolbar. |
modifier |
Modifier |
Modifier applied to the toolbar. |
backgroundColor |
Color |
Background color used by the toolbar. |
contentColor |
Color |
Color used for toolbar content. |
windowInsets |
WindowInsets |
Insets applied around the toolbar content. |
leading |
@Composable (RowScope.() -> Unit)? |
Optional leading content shown before the title. |
trailing |
@Composable (RowScope.() -> Unit)? |
Optional trailing content shown after the title. |
size |
ToolbarSize |
Size variant used by the standard toolbar. |
CenteredToolbar
A toolbar with centered title content.
@Composable
fun CenteredToolbar(
title: @Composable (RowScope.() -> Unit),
modifier: Modifier = Modifier,
leading: @Composable (RowScope.() -> Unit)? = null,
trailing: @Composable (RowScope.() -> Unit)? = null,
backgroundColor: Color = Color.Transparent,
contentColor: Color = LocalContentColor.current,
windowInsets: WindowInsets = toolbarWindowInsets(),
)
| Parameter | Type | Description |
|---|---|---|
title |
@Composable (RowScope.() -> Unit) |
Composable title content displayed by the toolbar. |
modifier |
Modifier |
Modifier applied to the toolbar. |
leading |
@Composable (RowScope.() -> Unit)? |
Optional leading content shown before the title. |
trailing |
@Composable (RowScope.() -> Unit)? |
Optional trailing content shown after the title. |
backgroundColor |
Color |
Background color used by the toolbar. |
contentColor |
Color |
Color used for toolbar content. |
windowInsets |
WindowInsets |
Insets applied around the toolbar content. |
ToolbarSize
Size variants for the standard Toolbar.
@JvmInline
value class ToolbarSize internal constructor(@Suppress("unused") private val value: Int)
| Value | Description |
|---|---|
Medium |
Uses the standard single-row toolbar height. |
Large |
Uses the large title toolbar layout. |