How to Create a Gradient SnackBar in Jetpack Compose for Kotlin Multiplatform
Create a visually appealing Gradient SnackBar in Jetpack Compose, compatible with Kotlin Multiplatform (KMP), to provide user feedback for actions like saving settings or confirming tasks across platforms.
Explore SnackBar Features
Learn to implement a customizable SnackBar with a gradient background, fade and scale animations, and an optional action button, designed for KMP projects.
- Gradient Background: Uses a green linear gradient for visual appeal.
- Animations: Smooth fade and scale effects for entry.
- Customizable: Configurable message, action label, and duration.
- KMP Compatible: Works across Android, iOS, and other platforms with Jetpack Compose.
Step-by-Step Implementation
Follow these steps to create a Gradient SnackBar in your KMP project:
- Define the Composable: Create the
GradientSnackBar
composable with parameters for message, action label, and duration. - Add Animations: Implement fade and scale animations using
animateFloatAsState
for smooth entry. - Style the Card: Use a
Card
with a transparent background and rounded corners for the SnackBar container. - Apply Gradient: Add a green linear gradient background to the
Row
layout. - Include Content: Add an icon, message text, and optional action button with consistent styling.
- Create a Demo: Build a demo composable to showcase the SnackBar with a trigger button and auto-dismiss functionality.
Step 1: Define the GradientSnackBar Composable
Start by defining the composable with customizable parameters for message, action label, duration, and a modifier.
@Composable
fun GradientSnackBar(
message: String = "Action completed successfully!", // Default message for feedback
actionLabel: String? = "Dismiss", // Optional action button text, nullable for no button
onAction: () -> Unit = {}, // Callback for action button click
duration: Long = 3000L, // Duration in milliseconds before auto-dismiss
modifier: Modifier = Modifier // Modifier for custom styling
) {
// Composable body will be built in subsequent steps
}
Step 2: Add Fade and Scale Animations
Implement animations to make the SnackBar appear smoothly with fade and scale effects.
@Composable
private fun animateFadeAndScaleSnackBar(): Pair {
val alpha by animateFloatAsState(
targetValue = 1f, // Fade to full opacity
animationSpec = tween(
durationMillis = SnackBarConstants.ALPHA_DURATION, // Duration for fade animation
easing = FastOutSlowInEasing // Smooth easing for natural effect
),
label = "alpha" // Label for animation debugging
)
val scale by animateFloatAsState(
targetValue = 1f, // Scale to full size
animationSpec = tween(
durationMillis = SnackBarConstants.SCALE_DURATION, // Duration for scale animation
easing = FastOutSlowInEasing // Smooth easing for natural effect
),
label = "scale" // Label for animation debugging
)
return alpha to scale // Return pair of animation values
}
Step 3: Style the Card Container
Use a Card
composable with animations and styling for the SnackBar's container.
Card(
modifier = modifier
.fillMaxWidth(0.9f) // Set width to 90% of parent
.wrapContentHeight() // Adjust height based on content
.alpha(alphaAnim) // Apply fade animation
.scale(scaleAnim) // Apply scale animation
.padding(SnackBarConstants.PADDING), // Add outer padding
shape = RoundedCornerShape(SnackBarConstants.CORNER_RADIUS), // Rounded corners
colors = CardDefaults.cardColors(containerColor = Color.Transparent), // Transparent for gradient
elevation = CardDefaults.cardElevation(defaultElevation = 6.dp) // Shadow for depth
) {
// Content will be added in the next step
}
Step 4: Apply Gradient and Build Row Layout
Add a green gradient background to a Row
layout to hold the SnackBar's content.
Row(
modifier = Modifier
.background(
Brush.linearGradient(
colors = listOf(Color(0xFF4CAF50), Color(0xFF81C784)) // Green gradient
)
)
.padding(SnackBarConstants.CONTENT_PADDING) // Inner padding
.height(IntrinsicSize.Min), // Minimum height based on content
verticalAlignment = Alignment.CenterVertically, // Center content vertically
horizontalArrangement = Arrangement.SpaceBetween // Space icon/message and button
) {
// Content will be added in the next step
}
Step 5: Add Icon, Message, and Action Button
Include an icon, message text, and an optional action button within the Row
.
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.weight(1f) // Take available space
) {
Icon(
imageVector = Icons.Rounded.Info, // Info icon
contentDescription = "Info", // Accessibility description
tint = Color.White, // White for contrast
modifier = Modifier
.size(SnackBarConstants.ICON_SIZE) // Fixed icon size
.padding(end = SnackBarConstants.ICON_PADDING) // Spacing after icon
)
Text(
text = message, // Provided message
color = Color.White, // White for contrast
fontSize = SnackBarConstants.MESSAGE_FONT_SIZE, // Font size
textAlign = TextAlign.Start, // Left-align
lineHeight = SnackBarConstants.MESSAGE_FONT_SIZE * 1.2f, // Line height
modifier = Modifier.padding(end = SnackBarConstants.SPACING) // Spacing
)
}
actionLabel?.let {
TextButton(
onClick = onAction, // Trigger action callback
modifier = Modifier.height(SnackBarConstants.BUTTON_HEIGHT) // Button height
) {
Text(
text = it, // Action label
color = Color.White, // White for contrast
fontSize = SnackBarConstants.ACTION_FONT_SIZE, // Font size
fontWeight = FontWeight.Medium // Medium weight
)
}
}
Step 6: Define Styling Constants and Demo
Create constants for consistent styling and a demo composable to showcase the SnackBar.
private object SnackBarConstants {
val PADDING = 8.dp // Outer padding
val CONTENT_PADDING = 12.dp // Inner padding
val CORNER_RADIUS = 12.dp // Corner radius
val ICON_SIZE = 24.dp // Icon size
val ICON_PADDING = 8.dp // Icon padding
val SPACING = 8.dp // Spacing between elements
val BUTTON_HEIGHT = 36.dp // Button height
val MESSAGE_FONT_SIZE = 14.sp // Message text size
val ACTION_FONT_SIZE = 14.sp // Action text size
const val ALPHA_DURATION = 250 // Fade duration (ms)
const val SCALE_DURATION =200 // Scale duration (ms)
}
@Composable
fun GradientSnackBarDemo() {
var showSnackBar by remember { mutableStateOf(false) } // State for visibility
Box(
modifier = Modifier
.fillMaxSize() // Fill screen
.padding(16.dp) // Outer padding
) {
Button(
onClick = { showSnackBar = true }, // Show SnackBar
modifier = Modifier
.align(Alignment.Center) // Center button
.wrapContentWidth() // Adjust width
) {
Text("Show SnackBar") // Button label
}
if (showSnackBar) {
Box(
modifier = Modifier
.align(Alignment.BottomCenter) // Bottom position
.padding(bottom = 16.dp) // Bottom padding
) {
GradientSnackBar(
message = "Your settings have been saved successfully!", // Demo message
actionLabel = "OK", // Action label
onAction = { showSnackBar = false }, // Dismiss on action
duration = 3000L // Auto-dismiss after 3 seconds
)
LaunchedEffect(Unit) {
delay(3000L) // Wait for duration
showSnackBar = false // Auto-dismiss
}
}
}
}
}
Full Source Code
Below is the complete implementation of the Gradient SnackBar for KMP projects.
package com.boltuix.compose.components
import androidx.compose.animation.core.FastOutSlowInEasing
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.tween
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.Info
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.scale
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import kotlinx.coroutines.delay
// Step 1: Define the GradientSnackBar composable with customizable parameters
@Composable
fun GradientSnackBar(
message: String = "Action completed successfully!", // Default message for feedback
actionLabel: String? = "Dismiss", // Optional action button text, nullable for no button
onAction: () -> Unit = {}, // Callback for action button click
duration: Long = 3000L, // Duration in milliseconds before auto-dismiss
modifier: Modifier = Modifier // Modifier for custom styling
) {
// Step 2: Fetch animation values for fade and scale effects
val (alphaAnim, scaleAnim) = animateFadeAndScaleSnackBar()
// Step 3: Create a Card container with animations and styling
Card(
modifier = modifier
.fillMaxWidth(0.9f) // Set width to 90% of parent
.wrapContentHeight() // Adjust height based on content
.alpha(alphaAnim) // Apply fade animation
.scale(scaleAnim) // Apply scale animation
.padding(SnackBarConstants.PADDING), // Add outer padding
shape = RoundedCornerShape(SnackBarConstants.CORNER_RADIUS), // Rounded corners
colors = CardDefaults.cardColors(containerColor = Color.Transparent), // Transparent for gradient
elevation = CardDefaults.cardElevation(defaultElevation = 6.dp) // Shadow for depth
) {
// Step 4: Build Row layout with gradient background and content
Row(
modifier = Modifier
.background(
Brush.linearGradient(
colors = listOf(Color(0xFF4CAF50), Color(0xFF81C784)) // Green gradient
)
)
.padding(SnackBarConstants.CONTENT_PADDING) // Inner padding
.height(IntrinsicSize.Min), // Minimum height based on content
verticalAlignment = Alignment.CenterVertically, // Center content vertically
horizontalArrangement = Arrangement.SpaceBetween // Space icon/message and button
) {
// Step 5: Nested Row for icon and message
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.weight(1f) // Take available space
) {
Icon(
imageVector = Icons.Rounded.Info, // Info icon
contentDescription = "Info", // Accessibility description
tint = Color.White, // White for contrast
modifier = Modifier
.size(SnackBarConstants.ICON_SIZE) // Fixed icon size
.padding(end = SnackBarConstants.ICON_PADDING) // Spacing after icon
)
Text(
text = message, // Provided message
color = Color.White, // White for contrast
fontSize = SnackBarConstants.MESSAGE_FONT_SIZE, // Font size
textAlign = TextAlign.Start, // Left-align
lineHeight = SnackBarConstants.MESSAGE_FONT_SIZE * 1.2f, // Line height
modifier = Modifier.padding(end = SnackBarConstants.SPACING) // Spacing
)
}
// Step 6: Add optional action button if actionLabel is provided
actionLabel?.let {
TextButton(
onClick = onAction, // Trigger action callback
modifier = Modifier.height(SnackBarConstants.BUTTON_HEIGHT) // Button height
) {
Text(
text = it, // Action label
color = Color.White, // White for contrast
fontSize = SnackBarConstants.ACTION_FONT_SIZE, // Font size
fontWeight = FontWeight.Medium // Medium weight
)
}
}
}
}
}
// Step 7: Implement fade and scale animations for smooth entry
@Composable
private fun animateFadeAndScaleSnackBar(): Pair {
val alpha by animateFloatAsState(
targetValue = 1f, // Fade to full opacity
animationSpec = tween(
durationMillis = SnackBarConstants.ALPHA_DURATION, // Duration for fade
easing = FastOutSlowInEasing // Smooth easing
),
label = "alpha" // Label for debugging
)
val scale by animateFloatAsState(
targetValue = 1f, // Scale to full size
animationSpec = tween(
durationMillis = SnackBarConstants.SCALE_DURATION, // Duration for scale
easing = FastOutSlowInEasing // Smooth easing
),
label = "scale" // Label for debugging
)
return alpha to scale // Return animation values
}
// Step 8: Define styling constants for consistent design
private object SnackBarConstants {
val PADDING = 8.dp // Outer padding
val CONTENT_PADDING = 12.dp // Inner padding
val CORNER_RADIUS = 12.dp // Corner radius
val ICON_SIZE = 24.dp // Icon size
val ICON_PADDING = 8.dp // Icon padding
val SPACING = 8.dp // Spacing between elements
val BUTTON_HEIGHT = 36.dp // Button height
val MESSAGE_FONT_SIZE = 14.sp // Message text size
val ACTION_FONT_SIZE = 14.sp // Action text size
const val ALPHA_DURATION = 250 // Fade duration (ms)
const val SCALE_DURATION = 200 // Scale duration (ms)
}
// Step 9: Demo composable to showcase the SnackBar
@Composable
fun GradientSnackBarDemo() {
var showSnackBar by remember { mutableStateOf(false) } // State for visibility
Box(
modifier = Modifier
.fillMaxSize() // Fill screen
.padding(16.dp) // Outer padding
) {
Button(
onClick = { showSnackBar = true }, // Show SnackBar
modifier = Modifier
.align(Alignment.Center) // Center button
.wrapContentWidth() // Adjust width
) {
Text("Show SnackBar") // Button label
}
if (showSnackBar) {
Box(
modifier = Modifier
.align(Alignment.BottomCenter) // Bottom position
.padding(bottom = 16.dp) // Bottom padding
) {
GradientSnackBar(
message = "Your settings have been saved successfully!", // Demo message
actionLabel = "OK", // Action label
onAction = { showSnackBar = false }, // Dismiss on action
duration = 3000L // Auto-dismiss after 3 seconds
)
LaunchedEffect(Unit) {
delay(3000L) // Wait for duration
showSnackBar = false // Auto-dismiss
}
}
}
}
}
Use the Gradient SnackBar to provide concise feedback for user actions with a modern, animated design in KMP projects.
Examples:
- Confirm settings saved in a cross-platform app
- Notify item added to a shopping cart on Android or iOS
- Alert successful form submission across platforms