Create a Custom Snackbar in Jetpack Compose (Success, Error, Warning)

Build a beautiful, reusable custom Snackbar component for Jetpack Compose that supports multiple message types with custom styling. This step-by-step tutorial shows you how to create Success, Error, Warning, and Default Snackbars with clean, production-ready code.

Create a Custom Snackbar in Jetpack Compose (Success, Error, Warning)
Photo by Nicolas J Leclercq / Unsplash

Learn how to build a beautiful, reusable custom Snackbar component in Jetpack Compose that supports different message types with custom styling. Perfect for displaying success messages, errors, warnings, and more!

📺 Are you a visual learner?

Check out the complete step-by-step video tutorial on YouTube where I walk you through building this custom Snackbar from scratch!

What We'll Build

By the end of this tutorial, you'll have a fully functional custom Snackbar system that:

  • Supports multiple types (Success, Error, Warning, Default)
  • Has custom colors and styling for each type
  • Is easily reusable across your entire project
  • Uses clean, production-ready code

Step 1: Define Snackbar Types

First, let's create an enum to define our different Snackbar types with their corresponding colors.

enum class SnackbarType(
    val containerColor: Color,
    val contentColor: Color = Color.White,
) {
    SUCCESS(containerColor = Color(0xFF4CAF50)),
    ERROR(containerColor = Color(0xFFF44336)),
    WARNING(containerColor = Color(0xFFFF9800)),
    DEFAULT(containerColor = Color.DarkGray);
}

Why this approach?

  • Each type has its own distinct color (green for success, red for error, orange for warning)
  • Content color defaults to white for better contrast
  • Easy to extend with more types if needed

Step 2: Create Custom Snackbar Visuals

Jetpack Compose's Snackbar system uses SnackbarVisuals to pass data. We'll create a custom implementation to include our type information.

data class CustomSnackbarVisuals(
    override val message: String,
    override val actionLabel: String? = null,
    override val duration: SnackbarDuration = SnackbarDuration.Short,
    override val withDismissAction: Boolean = false,
    val type: SnackbarType = SnackbarType.DEFAULT,
) : SnackbarVisuals

What's happening here?

  • We implement the SnackbarVisuals interface to integrate with Compose's Snackbar system
  • All standard Snackbar properties are included (message, duration, action label, etc.)
  • We add our custom type property to determine which style to use

Step 3: Create Extension Function for Easy Usage

To make our custom Snackbar easy to use throughout the app, we'll create an extension function on SnackbarHostState.

suspend fun SnackbarHostState.showCustomSnackbar(
    message: String,
    type: SnackbarType = SnackbarType.DEFAULT,
    actionLabel: String? = null,
    duration: SnackbarDuration = SnackbarDuration.Short,
    withDismissAction: Boolean = false,
): SnackbarResult {
    return showSnackbar(
        CustomSnackbarVisuals(
            message = message,
            actionLabel = actionLabel,
            duration = duration,
            withDismissAction = withDismissAction,
            type = type
        )
    )
}

Benefits of this approach:

  • Clean API that's easy to call from anywhere
  • Returns SnackbarResult so you can handle action button clicks

Step 4: Set Up the UI with Scaffold

Now let's implement the UI. We'll use a Scaffold with a custom SnackbarHost.

val snackbarHostState = remember {
    SnackbarHostState()
}
val scope = rememberCoroutineScope()

Scaffold(
    modifier = Modifier.fillMaxSize(),
    snackbarHost = {
        SnackbarHost(
            hostState = snackbarHostState,
            snackbar = { data ->
                val visuals = data.visuals as? CustomSnackbarVisuals
                val type = visuals?.type ?: SnackbarType.DEFAULT

                Snackbar(
                    snackbarData = data,
                    shape = RoundedCornerShape(16.dp),
                    containerColor = type.containerColor,
                    contentColor = type.contentColor
                )
            }
        )
    }
) { innerPadding ->
    // Your content here
}

Key points:

  • SnackbarHostState manages the Snackbar queue and display
  • rememberCoroutineScope() allows us to launch coroutines for showing Snackbars
  • The custom snackbar lambda extracts our custom type and applies the appropriate styling

Step 5: Display the Snackbar

To show a Snackbar, simply call the extension function we created:

Button(
    onClick = {
        scope.launch {
            snackbarHostState.currentSnackbarData?.dismiss()
            
            snackbarHostState.showCustomSnackbar(
                message = "Operation completed successfully!",
                type = SnackbarType.SUCCESS
            )
        }
    }
) {
    Text("Show Success Snackbar")
}

Important details:

  • Call currentSnackbarData?.dismiss() to dismiss any existing Snackbar before showing a new one
  • Launch in a coroutine scope since showCustomSnackbar is a suspend function
  • Simply change the type parameter to show different styled Snackbars

Complete Example with Multiple Buttons

Here's a complete example showing all Snackbar types:

Column(
    modifier = Modifier
        .fillMaxSize()
        .padding(innerPadding),
    horizontalAlignment = Alignment.CenterHorizontally,
    verticalArrangement = Arrangement.SpaceAround
) {
    SnackbarType.entries.forEach { type ->
        Button(
            onClick = {
                scope.launch {
                    snackbarHostState.currentSnackbarData?.dismiss()

                    snackbarHostState.showCustomSnackbar(
                        "This is a ${type.name} snackbar",
                        type = type
                    )
                }
            },
            colors = ButtonDefaults.buttonColors(
                containerColor = type.containerColor
            )
        ) {
            Text(type.name, color = Color.White)
        }
    }
}

Customization Ideas

Want to take this further? Here are some ideas:

1. Add Icons

enum class SnackbarType(
    val containerColor: Color,
    val contentColor: Color = Color.White,
    val icon: ImageVector
) {
    SUCCESS(
        containerColor = Color(0xFF4CAF50),
        icon = Icons.Default.CheckCircle
    ),
    ERROR(
        containerColor = Color(0xFFF44336),
        icon = Icons.Default.Error
    ),
    // ... more types
}

2. Custom Durations per Type

kotlin

enum class SnackbarType(
    val containerColor: Color,
    val contentColor: Color = Color.White,
    val defaultDuration: SnackbarDuration
) {
    SUCCESS(
        containerColor = Color(0xFF4CAF50),
        defaultDuration = SnackbarDuration.Short
    ),
    ERROR(
        containerColor = Color(0xFFF44336),
        defaultDuration = SnackbarDuration.Long
    ),
    // ...
}

3. Add Animations

Wrap your Snackbar in AnimatedVisibility for custom enter/exit animations.

Common Use Cases

Success Messages:

snackbarHostState.showCustomSnackbar(
    "Profile updated successfully!",
    type = SnackbarType.SUCCESS
)

Error Messages:

snackbarHostState.showCustomSnackbar(
    "Failed to load data. Please try again.",
    type = SnackbarType.ERROR,
    actionLabel = "Retry"
)

Warning Messages:

snackbarHostState.showCustomSnackbar(
    "Low battery. Some features may be limited.",
    type = SnackbarType.WARNING,
    duration = SnackbarDuration.Long
)

Conclusion

You now have a fully functional, reusable custom Snackbar system for your Jetpack Compose app! This implementation is:

  • Clean: Simple enum-based type system
  • Flexible: Easy to extend with new types or features
  • Reusable: Works across your entire app
  • Modern: Uses Jetpack Compose best practices

The complete source code is available on GitHub. Feel free to customize the colors, add animations, or extend it with additional features!


Found this helpful? Subscribe to my YouTube channel for more Android and Jetpack Compose tutorials!

Questions or suggestions? Drop a comment below!