compose-sonner
0.4.0indexedOpinionated toast component offering stacked toasts, animations, various types, transitions, customizable icons, and swipe-to-dismiss functionality. Supports lazy rendering and Material design themes.
Opinionated toast component offering stacked toasts, animations, various types, transitions, customizable icons, and swipe-to-dismiss functionality. Supports lazy rendering and Material design themes.
An opinionated toast component for Compose Multiplatform.
This is a Compose implementation of sonner - an excellent toast library by emilkowalski. This is a fork of dokar3/compose-sonner with modifications by brdominguez. Original project licensed under Apache 2.0.
WasmJs Demo
https://brdominguez.github.io/compose-sonner/
Video
https://github.com/dokar3/compose-sonner/assets/68095777/ff97c6cc-012e-4152-8c40-0d2ba382c757
build.gradle(.kts)
implementation("io.github.brdominguez:compose-sonner:<VERSION>")
libs.versions.toml
sonner = { module = "io.github.brdominguez:compose-sonner", version = "<VERSION>" }
val toaster = rememberToasterState()
Button(onClick = { toaster.show("Hello world!") }) {
Text("Show a toast")
}
Toaster(state = toaster)
toaster.show(
message = "Message",
type = ToastType.[Normal | Success | Info | Warning | Error],
)
toaster.show(
message = "Message",
darkTheme = true,
)
Toaster(
state = toaster,
alignment = Alignment.[TopStart | TopCenter | TopEnd | BottomStart | BottomCener | BottomEnd],
)
toaster.show(
message = "Message",
duration = [ToasterDefaults.DurationLong | 5000.milliseconds | Duration.INFINITE],
)
const val TOAST_ID_LOADING = ANYTHING
toaster.show(message = "Loading", id = TOAST_ID_LOADING, duration = Duration.INFINITE)
toaster.show(message = "Success", id = TOAST_ID_LOADING)
toaster.dismiss(id)
toaster.dismiss(toast)
toaster.dismissAll()
// Enable close buttons
Toaster(
state = toaster,
showCloseButton = true,
)
// Set an action button
toaster.show(
message = "Message",
action = TextToastAction(
text = "Dismiss",
onClick = { toaster.dismiss(it) },
)
)
Toaster(
state = toaster,
iconSlot = { toast ->
// ICON_LOADING can be anything, it's just a mark
if (toast.icon == ICON_LAODING) {
LoadingIcon()
} else {
// Fallback to the default icon slot
ToasterDefaults.iconSlot(toast)
}
},
)
Toaster(
state = toaster,
dismissPause = ToastDismissPause.[Never | OnNotFront | OnInvisible],
)
Define the mapping function
fun UiMessage.toToast(): Toast = when (this) {
is UiMessage.Error -> Toast(id = id, message = message, type = ToastType.Error)
is UiMessage.Success -> Toast(id = id, message = message, type = ToastType.Success)
}
Then
val toaster = rememberToasterState(
onDismissed = { viewModel.removeUiMessageById(it.id as Long) },
)
val uiState by viewModel.uiState.collectAsState()
LaunchedEffect(viewModel, toaster) {
// Listen to messages changes and map to toasts
val toastsFlow = viewModel.uiState.map { it.uiMessages.map(UiMessage::toToast) }
toaster.listenMany(toastsFlow)
}
Toaster(state = toaster)
Or use an adapter function
Modifications Copyright 2025 brdominguez
Copyright 2024 dokar3
Licensed under the Apache License, Version 2.0 (the "License");
you may not use except compliance the License.
You may obtain a copy of the License at
http:
Unless applicable law agreed to writing, software
distributed under the License distributed an BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express implied.
See the License the specific language governing permissions
limitations under the License.
fun Toaster(
contentColor: @Composable (toast: Toast) -> Color,
border: @Composable (toast: Toast) -> BorderStroke,
background: @Composable (toast: Toast) -> Brush,
shape: @Composable (toast: Toast) -> Shape,
elevation: Dp,
shadowAmbientColor: Color,
shadowSpotColor: Color,
contentPadding: PaddingValues,
containerPadding: @Composable (toast: Toast) -> PaddingValues,
widthPolicy: @Composable (toast: Toast) -> ToastWidthPolicy,
offset: IntOffet,
)
fun UiMessageToaster(
messages: List<UiMessage>,
onRemoveMessage: (id: Long) -> Unit,
modifier: Modifier = Modifier,
) {
val toaster = rememberToasterState(
onToastDismissed = { onRemoveMessage(it.id as Long) },
)
val currentMessages by rememberUpdatedState(messages)
LaunchedEffect(toaster) {
// Listen to State<List<UiMessage>> changes and map to toasts
toaster.listenMany { currentMessages.map(UiMessage::toToast) }
}
Toaster(state = toaster, modifier = modifier)
}
fun YourScreen(...) {
val uiState by viewModel.uiState.collectAsState()
...
UiMessageToaster(
messages = uiState.uiMessages,
onRemoveMessage = { viewModel.removeUiMessageById(it) },
)
}
Surfaced from shared tags and platforms — no rankings paid for.