island_snack
Dynamic Island-style notifications for Flutter iOS apps. Expands from the island position with spring physics animations, staggered content reveal, and swipe-to-dismiss.
On devices with a Dynamic Island the pill starts at the exact hardware cutout position. On older devices it falls back to a centered top-of-screen pill.
Features
- Spring physics expand/collapse (underdamped overshoot + critically-damped retract)
- Staggered per-element content reveal (icon, title, subtitle, action)
- Preset styles:
success,error,warning,info,loading - Queue system with normal and urgent priority
- ID-based in-place updates (e.g. loading -> success)
- Swipe-to-dismiss
- Action buttons, progress bars, custom leading widgets
IslandSnackThemefor global color/font/duration/haptic overridesonDismissed/onShowncallbacks- Accessibility: VoiceOver live region, semantic labels
Platform support
| Platform | Supported |
|---|---|
| iOS | Yes |
| Android | No |
| Web | No |
Installation
dependencies:
island_snack: ^0.1.0
Quick start
Wrap your app with IslandSnackTheme (optional) and call the static methods:
import 'package:island_snack/island_snack.dart';
// Optional: wrap your app for global theming
IslandSnackTheme(
successColor: Color(0xFF7E9B72),
fontFamily: 'SF Pro',
child: MaterialApp(...),
);
// Show notifications from anywhere
IslandSnack.success(context, title: 'Saved');
IslandSnack.error(context, title: 'Upload failed', subtitle: 'Try again');
IslandSnack.warning(context, title: 'Offline');
IslandSnack.info(context, title: '12 items archived');
IslandSnack.loading(context, id: 'sync', title: 'Syncing...');
// Dismiss
IslandSnack.dismiss();
IslandSnack.dismissAll();
API reference
Preset methods
| Method | Icon | Default color | Default haptic |
|---|---|---|---|
IslandSnack.success() |
checkmark | #7E9B72 sage |
light |
IslandSnack.error() |
x-circle | #B87068 rose |
medium |
IslandSnack.warning() |
warning | #C9834E terracotta |
light |
IslandSnack.info() |
info | #C8B9A0 sand |
light |
IslandSnack.loading() |
spinner | #8E8E93 gray |
none |
All preset methods accept:
IslandSnack.success(
context,
title: 'Saved', // required
subtitle: 'To favorites', // optional
id: 'save', // optional — for in-place updates
duration: IslandSnackDuration.long,
customDuration: Duration(seconds: 4), // overrides enum duration
haptic: IslandSnackHaptic.light,
priority: IslandSnackPriority.normal,
action: IslandSnackAction(label: 'Undo', onPressed: () {}),
leading: Icon(Icons.star), // custom leading widget
onTap: () {},
onDismissed: () {},
onShown: () {},
);
IslandSnack.show()
Full control — same parameters as above plus:
IslandSnack.show(
context,
title: 'Processing',
style: IslandSnackStyle(
accentColor: Colors.purple,
iconData: Icons.auto_awesome,
),
progress: 0.65, // 0.0 to 1.0
isLoading: true, // shows spinner instead of icon
);
Dismiss
IslandSnack.dismiss(); // dismiss current
IslandSnack.dismissAll(); // dismiss current + clear queue
State getters
IslandSnack.isShowing; // bool — whether a notification is visible
IslandSnack.isDynamicIsland; // bool? — null if not yet detected
ID-based updates
Assign the same id to replace a notification in-place:
IslandSnack.loading(context, id: 'upload', title: 'Uploading...');
// Later:
IslandSnack.success(context, id: 'upload', title: 'Upload complete!');
Queue behavior
- Normal priority: queued — shows after the current notification dismisses.
- Urgent priority: clears queue and replaces current immediately.
- ID-based: if a notification with the same ID is showing, it is dismissed and replaced.
Customization
Theme
IslandSnackTheme(
successColor: Color(0xFF7E9B72),
errorColor: Color(0xFFB87068),
warningColor: Color(0xFFC9834E),
infoColor: Color(0xFFC8B9A0),
loadingColor: Color(0xFF8E8E93),
fontFamily: 'SF Pro',
shortDuration: Duration(seconds: 2),
longDuration: Duration(seconds: 3),
actionDuration: Duration(seconds: 5),
defaultHaptic: IslandSnackHaptic.light,
child: MaterialApp(...),
);
Custom style
IslandSnack.show(
context,
title: 'Custom',
style: IslandSnackStyle(
accentColor: Colors.purple,
iconData: Icons.auto_awesome,
),
);
Callbacks
IslandSnack.success(
context,
title: 'Done',
onShown: () => print('Visible'),
onDismissed: () => print('Gone'),
);
Accessibility
- The notification pill is wrapped in a
Semanticswidget withliveRegion: true, so VoiceOver announces it automatically. - Action buttons have
Semantics(button: true). - Decorative elements (accent line, debug overlay) are excluded from the semantic tree.
License
MIT — see LICENSE.
Libraries
- island_snack
- Dynamic Island-style notifications for Flutter iOS apps.