Badges display small amounts of information, typically notification counts or status indicators. They’re often placed on top of other UI elements like icons or avatars to draw attention to updates or changes.
Usage
import com.nomanr.lumo.ui.components.Badge
import com.nomanr.lumo.ui.components.BadgedBox
// Badge with content (notification count)
BadgedBox(
badge = {
Badge { Text("5") }
}
) {
Icon(Icons.Default.Notifications, "Notifications")
}
// Badge without content (indicator dot)
BadgedBox(
badge = { Badge() }
) {
Icon(Icons.Default.Message, "Messages")
}
Variants
Notification Badge
Show notification counts:
BadgedBox(
badge = {
Badge {
Text("12")
}
}
) {
IconButton(onClick = { }) {
Icon(Icons.Default.Email, "Email")
}
}
Indicator Dot
Simple presence indicator without content:
BadgedBox(
badge = {
Badge() // Empty badge shows as a small dot
}
) {
Icon(Icons.Default.Person, "Profile")
}
Custom Colors
Use different colors for different states:
// Error/urgent notifications
BadgedBox(
badge = {
Badge(
containerColor = AppTheme.colors.error,
contentColor = AppTheme.colors.onError
) {
Text("3")
}
}
) {
Icon(Icons.Default.Notifications, "Alerts")
}
// Success indicator
BadgedBox(
badge = {
Badge(
containerColor = Color.Green,
contentColor = Color.White
)
}
) {
Icon(Icons.Default.Check, "Online")
}
With Avatar
Common pattern for user avatars:
BadgedBox(
badge = {
Badge(
containerColor = Color.Green
) // Online indicator
}
) {
Box(
modifier = Modifier
.size(48.dp)
.clip(CircleShape)
.background(AppTheme.colors.primary)
) {
Icon(
Icons.Default.Person,
contentDescription = "User",
modifier = Modifier.align(Alignment.Center)
)
}
}
In Navigation
NavigationBarItem(
icon = {
BadgedBox(
badge = {
if (unreadCount > 0) {
Badge {
Text(unreadCount.toString())
}
}
}
) {
Icon(Icons.Default.Message, "Messages")
}
},
selected = selectedTab == 2,
onClick = { selectedTab = 2 }
)
Parameters
BadgedBox
badge
@Composable BoxScope.() -> Unit
required
The badge content to overlay on top of the content
modifier
Modifier
default:"Modifier"
Modifier for the BadgedBox container
content
@Composable BoxScope.() -> Unit
required
The anchor content that the badge overlays
Badge
modifier
Modifier
default:"Modifier"
Modifier for the badge
containerColor
Color
default:"AppTheme.colors.error"
Background color of the badge
contentColor
Color
default:"contentColorFor(containerColor)"
Color for the badge content text
content
@Composable (RowScope.() -> Unit)?
default:"null"
Optional content to display in the badge (e.g., notification count)
Styling
Custom Size Badge
Adjust badge size with modifier:
BadgedBox(
badge = {
Badge(
modifier = Modifier.size(12.dp)
)
}
) {
Icon(Icons.Default.Star, "Favorite")
}
Text Styling
Badge text uses AppTheme.typography.label3 by default:
Badge {
Text(
text = "99+",
style = AppTheme.typography.label3
)
}
Shape Customization
Badges use RoundedCornerShape(50%) by default (circular):
// Default shape is fully rounded
Badge {
Text("5")
}
Size Specifications
Badge without Content (Dot)
- Size: 8.dp × 8.dp
- Horizontal Offset: -4.dp (half of badge size)
- Vertical Offset: 0.dp
Badge with Content
- Min Size: 18.dp × 18.dp
- Horizontal Padding: 4.dp
- Vertical Padding: 2.dp
- Horizontal Offset: -6.dp
- Vertical Offset: 0.dp
Positioning
Badges are positioned at the top-end corner of the anchor element:
// Badge automatically positions itself
BadgedBox(
badge = { Badge { Text("N") } }
) {
Icon(Icons.Default.ShoppingCart, "Cart")
}
// Visual result:
// ┌────────┐
// │ Icon (N) ← Badge
// └────────┘
The positioning is intelligent and respects screen boundaries:
- Adjusts horizontally if too close to screen edge
- Adjusts vertically if too close to screen top
Use Cases
Message Notifications
val unreadMessages = 5
BadgedBox(
badge = {
if (unreadMessages > 0) {
Badge {
Text(
if (unreadMessages > 99) "99+" else unreadMessages.toString()
)
}
}
}
) {
Icon(Icons.Default.ChatBubble, "Messages")
}
val itemCount = cartItems.size
BadgedBox(
badge = {
if (itemCount > 0) {
Badge(
containerColor = AppTheme.colors.primary
) {
Text(itemCount.toString())
}
}
}
) {
IconButton(onClick = { navigateToCart() }) {
Icon(Icons.Default.ShoppingCart, "Cart")
}
}
Online Status
BadgedBox(
badge = {
Badge(
containerColor = when (userStatus) {
Status.Online -> Color.Green
Status.Away -> Color.Yellow
Status.Busy -> Color.Red
Status.Offline -> Color.Gray
}
)
}
) {
UserAvatar(user)
}
New Feature Indicator
BadgedBox(
badge = {
Badge(
containerColor = Color.Blue
) {
Text("New", style = AppTheme.typography.label3)
}
},
modifier = Modifier.padding(8.dp)
) {
Button(onClick = { }) {
Text("Settings")
}
}
Best Practices
- Keep Numbers Small: Use “99+” for counts over 99
- Color Meaning: Use red for urgent, blue for info, green for positive
- Visibility: Ensure good contrast between badge and background
- Size: Don’t make badges too large - they should complement, not dominate
- Empty States: Hide badges when count is 0
- Accessibility: Badge content is automatically read by screen readers
- Animation: Consider animating badge appearance for better UX
Animation Example
Add entrance animation:
var visible by remember { mutableStateOf(false) }
BadgedBox(
badge = {
AnimatedVisibility(
visible = visible,
enter = scaleIn() + fadeIn(),
exit = scaleOut() + fadeOut()
) {
Badge { Text("1") }
}
}
) {
Icon(Icons.Default.Notifications, "Notifications")
}
Accessibility
- Badge content is automatically announced by screen readers
- Combines with the anchor element’s content description
- For indicator dots, consider adding a semantic description
BadgedBox(
badge = {
Badge(
modifier = Modifier.semantics {
contentDescription = "3 new notifications"
}
)
}
) {
Icon(Icons.Default.Notifications, "Notifications")
}
Source Reference
See the full implementation in Badge.kt:44-123 (BadgedBox) and Badge.kt:126-166 (Badge).