
zentoast
A headless, fully customizable toast system for Flutter. You design the UI β zentoast takes care of animation, physics, queuing, gestures, and multi-position viewers. Perfect for building Sonner-like toasts, message banners, or fully custom notification UIs.

Features
- β¨ Headless Architecture β Bring your own widgets & design
- π― Flexible Positioning β Display toasts anywhere on screen
- π¨ Extremely Customizable β Full control over layout, styling & behavior
- π Fluid Animations β Motor-powered, physics-based animation system
- π Rich Gestures β Drag to dismiss, tap to pause, swipe interactions
- π§ Theming Support β Global settings via
ToastTheme - π¦ Multiple Viewers β Independent stacks with synchronized smoothness
Installation
Add to your pubspec.yaml:
dependencies:
zentoast: ^latest_version
Import:
import 'package:zentoast/zentoast.dart';
Quick Start
Wrap your app with ToastProvider and configure a ToastViewer:
void main() {
runApp(
ToastProvider.create(
child: MyApp(),
),
);
}
Minimal Example
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
builder: (context, child) => ToastThemeProvider(
data: ToastTheme(
gap: 8,
viewerPadding: EdgeInsets.all(12),
),
child: Stack(
children: [
Positioned.fill(child: child ?? SizedBox()),
SafeArea(
child: ToastViewer(
alignment: Alignment.topRight,
delay: Duration(seconds: 3),
visibleCount: 3,
),
),
],
),
),
home: HomePage(),
);
}
}
Triggering a Toast
ElevatedButton(
onPressed: () {
Toast(
height: 64,
builder: (toast) => Container(
padding: EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.green,
borderRadius: BorderRadius.circular(8),
),
child: Row(
children: [
Icon(Icons.check, color: Colors.white),
SizedBox(width: 12),
Expanded(
child: Text(
'Success! Your changes have been saved.',
style: TextStyle(color: Colors.white),
),
),
IconButton(
icon: Icon(Icons.close, color: Colors.white),
onPressed: () => toast.hide(context),
),
],
),
),
).show(context);
},
child: Text('Show Toast'),
)
Building Your Own Toast UI
zentoast is headless, meaning you provide the UI.
Hereβs a custom toast example:
class CustomToast extends StatelessWidget {
const CustomToast({
super.key,
required this.title,
required this.message,
required this.onClose,
this.icon,
this.color = Colors.blue,
});
final String title;
final String message;
final VoidCallback onClose;
final IconData? icon;
final Color color;
@override
Widget build(BuildContext context) {
return Material(
color: Colors.transparent,
child: Container(
padding: EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 10,
offset: Offset(0, 4),
),
],
border: Border(
left: BorderSide(color: color, width: 4),
),
),
child: Row(
children: [
if (icon != null) ...[
Icon(icon, color: color),
SizedBox(width: 12),
],
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(title,
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16,
)),
SizedBox(height: 4),
Text(message,
style: TextStyle(
color: Colors.grey[600],
fontSize: 14,
)),
],
),
),
IconButton(
icon: Icon(Icons.close, size: 20),
onPressed: onClose,
),
],
),
),
);
}
}
Usage:
Toast(
height: 80,
builder: (toast) => CustomToast(
title: 'New Message',
message: 'You have received a new message from John',
icon: Icons.message,
color: Colors.purple,
onClose: () => toast.hide(context),
),
).show(context);
Positioning
Place toasts anywhere with alignment:
ToastViewer(alignment: Alignment.topLeft)
ToastViewer(alignment: Alignment.bottomCenter)
ToastViewer(alignment: Alignment.topRight)
ToastViewer(alignment: Alignment.bottomRight)
Multiple Viewers
You can show independent toasts in multiple corners for each toast category:
Stack(
children: [
Positioned.fill(child: child),
SafeArea(
child: ToastViewer(
alignment: Alignment.topRight,
delay: Duration(seconds: 3),
// Display all toast no filter
categories: null,
),
),
SafeArea(
child: ToastViewer(
alignment: Alignment.bottomCenter,
delay: Duration(seconds: 5),
// Display only `Toast` with specific category
categories: [
ToastCategory.success,
ToastCategory.error,
],
),
),
],
)
Animations stay smooth even when dismissing multiple stacks simultaneously.
Categorize Toast
Organize notifications by ToastCategory so each ToastViewer can focus on
the messages it cares about. Every toast defaults to ToastCategory.general,
and you can introduce new categories with a simple const ToastCategory('name').
const billingCategory = ToastCategory('billing');
void _showCategorizedToasts(BuildContext context) {
Toast(
category: ToastCategory.success,
builder: (toast) => SuccessToast(onClose: () => toast.hide(context)),
).show(context);
Toast(
category: ToastCategory.error,
builder: (toast) => ErrorToast(onClose: () => toast.hide(context)),
).show(context);
Toast(
category: billingCategory,
builder: (toast) => BillingToast(onClose: () => toast.hide(context)),
).show(context);
}
Widget build(BuildContext context) {
return ToastProvider.create(
child: Stack(
children: [
ToastViewer(
alignment: Alignment.topRight,
categories: const [
ToastCategory.success,
ToastCategory.error,
],
),
ToastViewer(
alignment: Alignment.bottomLeft,
categories: const [billingCategory],
delay: const Duration(milliseconds: 300),
),
],
),
);
}
With this setup, success and error notifications render at the top-right, while billing alerts stay anchored at the bottom-left with a custom delay.
Theming
ToastThemeProvider(
data: ToastTheme(
gap: 12,
viewerPadding: EdgeInsets.all(16),
),
child: YourApp(),
)
Advanced Configuration
Toast(
height: 100,
category: ToastCategory.success, // Config customize category
builder: (toast) => YourToastWidget(
onClose: () => toast.hide(context),
),
);
ToastViewer(
alignment: Alignment.topRight,
delay: Duration(seconds: 4),
visibleCount: 3,
categories: [ToastCategory.success, ToastCategory('card')],
);
Gesture Support
zentoast includes gesture interaction with no extra setup:
- Swipe to dismiss (vertical)
- Touch to pause auto-dismiss
- Drag to remove
- Smooth physics response powered by motor
Example App
See /example for:
- Sonner-like toasts
- Brutalist / Card variants
- Multi-position demos
- Gesture demos
- Advanced theming and animations
API Overview
Core Classes
Toastβ Creates a toast instanceToastProviderβ Global manager for the toast stackToastViewerβ Renders a toast queue with animationsToastThemeβ Global styling configToastThemeProviderβ Provides theme to descendants
Key Methods
Toast.show(context)β Show a toastToast.hide(context)β Hide the toastToastProvider.of(context)β Access provider manually
Contributing
Contributions are welcome! Please open an issue or submit a PR.
License
MIT License. See LICENSE for details.