A production-ready Flutter button widget with built-in loading animations and status feedback (Success/Error). Perfect for asynchronous operations and form submissions.
- β³ Loading State β Built-in progress indicator; button auto-disables during execution.
- β Status Feedback β Success/Error icons shown based on operation result.
- π¨ Deeply Customizable β Colors, radius, borders, shadows, animations, icons, and more.
- π Theme Support β Global defaults via
OnProcessButtonDefaultValuesorOnProcessButtonTheme. - π« Smooth Animations β Animated size transitions between states.
- π± Platform Ready β Android, iOS, Web, macOS, Windows, and Linux.
flutter pub add on_process_button_widgetOr add to pubspec.yaml:
dependencies:
on_process_button_widget: ^2.0.12import 'package:on_process_button_widget/on_process_button_widget.dart';onTap runs your async operation. Return true for success, false for error, or null to skip status display entirely.
OnProcessButtonWidget(
onTap: () async {
await submitForm();
return true;
},
child: const Text('Submit'),
)Uses Theme.of(context).colorScheme.primary as background and onPrimary for text/icons automatically. No manual color styling needed on Text() or Icon() children β the button controls all color.
OnProcessButtonWidget(
onTap: () async {
await Future.delayed(const Duration(seconds: 2));
return true;
},
child: const Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.download),
SizedBox(width: 8),
Text('Download'),
],
),
)OnProcessButtonWidget(
expanded: false,
border: Border.all(color: Theme.of(context).colorScheme.outline),
backgroundColor: Colors.transparent,
iconColor: Theme.of(context).colorScheme.onSurface,
fontColor: Theme.of(context).colorScheme.onSurface,
fontWeight: FontWeight.normal,
onTap: () async => true,
child: const Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.sync, size: 18),
SizedBox(width: 8),
Text('Refresh'),
],
),
)OnProcessButtonWidget(
expanded: false,
border: Border.all(color: Colors.red),
backgroundColor: Colors.red.withValues(alpha: 0.1),
iconColor: Colors.red,
fontColor: Colors.red,
fontWeight: FontWeight.normal,
onTap: () async {
await performDangerousAction();
return true;
},
child: const Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.delete, size: 18),
SizedBox(width: 8),
Text('Delete'),
],
),
)OnProcessButtonWidget(
enable: false,
onTap: () async => true,
child: const Text('Disabled'),
)Set isRunning: true to force the loading state without calling onTap. The callback won't be invoked.
OnProcessButtonWidget(
isRunning: isLoading,
child: const Text('Submit'),
)| Style | backgroundColor |
border |
iconColor |
fontColor |
|---|---|---|---|---|
| Filled (default) | (theme primary) | (none) | (theme onPrimary) | (theme onPrimary) |
| Outlined | Colors.transparent |
Border.all(...) |
onSurface |
onSurface |
| Colored action | color.withAlpha(25) |
Border.all(color) |
same color |
same color |
β οΈ Important: Never setcolor:orstyle:directly onText()orIcon()children. The button controls text/icon appearance via its own properties (fontColor,iconColor,textStyle,fontWeight). Manual colors will override the button's color system.
| Property | Type | Default | Description |
|---|---|---|---|
onTap |
Future<bool?> Function()? |
null |
Async handler. Return true=success, false=error, null=skip status. |
onDone |
void Function(bool?)? |
null |
Called after tap completes and status finishes displaying. |
onStatusChange |
void Function(BuildContext?, OnProcessButtonStatus)? |
null |
Called when button status changes (stable/running/success/error). |
child |
Widget? |
null |
Primary button content (Text, Row with Icon+Text, etc.). |
expanded |
bool |
true |
Whether button fills available width. |
backgroundColor |
Color? |
theme primary | Background color. |
iconColor |
Color? |
theme onPrimary | Color of status indicators AND child icons. |
fontColor |
Color? |
theme onPrimary | Color of button text. |
fontWeight |
FontWeight? |
bold | Font weight. |
textStyle |
TextStyle? |
titleMedium | Full text style override. |
border |
BoxBorder? |
null |
Border (set for outlined style). |
borderRadius |
BorderRadius? |
8.0 circular | Corner radius. |
boxShadow |
List<BoxShadow>? |
null |
Shadow under the button. |
contentPadding |
EdgeInsetsGeometry? |
horiz:12, vert:4 | Inner padding. |
enable |
bool? |
true |
Whether button is interactive. |
onRunningWidget |
Widget? |
CircularProgressIndicator |
Shown during loading. |
onSuccessWidget |
Widget? |
Icons.done |
Shown on success. |
onErrorWidget |
Widget? |
Icons.error |
Shown on error. |
showRunningStatusWidget |
bool? |
true |
Whether to show the running status widget. |
roundBorderWhenRunning |
bool? |
true |
Circular border during non-stable states. |
expandedIcon |
bool? |
null |
Whether status icon fills available width. |
statusShowingDuration |
Duration? |
2 seconds | How long success/error is displayed. |
isRunning |
bool |
false |
Manually force running/loading state. |
For the complete property reference including interaction callbacks, mouse/focus/highlight, animation, and text styling, see the API Reference section.
The button provides dedicated properties for all visual aspects. Do not style Text() or Icon() children directly.
OnProcessButtonWidget(
backgroundColor: Colors.deepPurple,
iconColor: Colors.white,
fontColor: Colors.white,
borderRadius: BorderRadius.circular(12),
contentPadding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
onTap: () async => true,
child: const Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.save),
SizedBox(width: 8),
Text('Save Changes'),
],
),
)Your controller methods should return Future<bool?>:
class AuthController {
Future<bool?> login() async {
try {
await authService.login(email, password);
return true;
} catch (e) {
return false;
}
}
}
// In your view:
OnProcessButtonWidget(
onTap: controller.login,
child: const Text('Login'),
)Show a confirmation dialog before executing, or chain operations:
OnProcessButtonWidget(
onTap: () async {
final confirmed = await showDialog<bool>(
context: context,
builder: (ctx) => AlertDialog(
title: const Text('Confirm'),
content: const Text('Proceed with this action?'),
actions: [
TextButton(onPressed: () => Navigator.pop(ctx, false), child: const Text('Cancel')),
TextButton(onPressed: () => Navigator.pop(ctx, true), child: const Text('OK')),
],
),
);
if (confirmed != true) return false;
await controller.dangerousAction();
return true;
},
onDone: (isSuccess) {
if (isSuccess == true) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Action completed')),
);
}
},
child: const Text('Dangerous Action'),
)Set app-wide defaults before runApp. Properties are static and persist for the application lifetime.
void main() {
OnProcessButtonDefaultValues.borderRadius = BorderRadius.circular(12);
OnProcessButtonDefaultValues.expandedIcon = true;
OnProcessButtonDefaultValues.roundBorderWhenRunning = false;
OnProcessButtonDefaultValues.onStatusChange = (context, status) {
if (context == null) return;
if (status == OnProcessButtonStatus.running) {
showDialog(
context: context,
barrierDismissible: false,
builder: (_) => const AlertDialog(
title: Text('Processing'),
content: Text('Please wait...'),
),
);
} else if (status == OnProcessButtonStatus.stable) {
Navigator.of(context).pop();
}
};
runApp(const MyApp());
}Scoped defaults via inherited widget. Resolution order: widget param β OnProcessButtonTheme.of(context) β OnProcessButtonDefaultValues β default.
OnProcessButtonThemeProvider(
data: const OnProcessButtonThemeData(
borderRadius: BorderRadius.all(Radius.circular(16)),
iconColor: Colors.white,
),
child: MyWidget(),
)| All Features | Hover Effects |
|---|---|
![]() |
![]() |
| Request Status | Double Process |
![]() |
![]() |
| Custom Styles | Card Mode |
![]() |
![]() |
enum OnProcessButtonStatus { stable, running, success, error }| Value | Meaning |
|---|---|
stable |
Idle state, showing child. |
running |
onTap is executing, showing onRunningWidget. |
success |
onTap returned true, showing onSuccessWidget. |
error |
onTap returned false, showing onErrorWidget. |
The main button widget. A StatefulWidget with all properties passed via the constructor.
stable β (tap) β running β (result=true) β success β (delay) β stable
β (result=false) β error β (delay) β stable
β (result=null) β stable (skip)
| Property | Type | Default |
|---|---|---|
onTap |
Future<bool?>? Function()? |
null |
onDone |
void Function(bool?)? |
null |
onStatusChange |
void Function(BuildContext?, OnProcessButtonStatus)? |
null |
| Property | Type | Default |
|---|---|---|
onLongPress |
void Function()? |
null |
onTapUp |
void Function(TapUpDetails)? |
null |
onTapDown |
void Function(TapDownDetails)? |
null |
onTapCancel |
void Function()? |
null |
onDoubleTap |
void Function()? |
null |
onSecondaryTap |
void Function()? |
null |
onSecondaryTapUp |
void Function(TapUpDetails)? |
null |
onSecondaryTapDown |
void Function(TapDownDetails)? |
null |
onSecondaryTapCancel |
void Function()? |
null |
| Property | Type | Default |
|---|---|---|
onHover |
void Function(bool isEnter)? |
null |
onHovering |
void Function(PointerHoverEvent)? |
null |
onFocusChange |
void Function(bool isFocused)? |
null |
onHighlightChanged |
void Function(bool isHighlighted)? |
null |
mouseCursor |
MouseCursor? |
null |
focusNode |
FocusNode? |
null |
| Property | Type | Default | Notes |
|---|---|---|---|
backgroundColor |
Color? |
theme primary | Runtime theme fallback |
iconColor |
Color? |
theme onPrimary | Controls status icons AND child icons |
fontColor |
Color? |
theme onPrimary | |
fontWeight |
FontWeight? |
bold |
|
textStyle |
TextStyle? |
titleMedium |
Full override; if null, builds from fontColor + fontWeight |
border |
BoxBorder? |
null |
|
borderRadius |
BorderRadius? |
8.0 circular |
|
boxShadow |
List<BoxShadow>? |
null |
When set, background becomes surface color |
focusColor |
Color? |
null |
|
splashColor |
Color? |
null |
|
highlightColor |
Color? |
null |
|
hoverColor |
Color? |
null |
|
useMaterial3 |
bool? |
true |
Controls which theme colors are referenced |
| Property | Type | Default | Notes |
|---|---|---|---|
expanded |
bool |
true |
Fills width when true |
expandedIcon |
bool? |
null |
Falls back to expanded |
enable |
bool? |
true |
Disabled = onTap becomes null |
enableFeedback |
bool? |
true |
|
autofocus |
bool? |
true |
|
width |
double? |
null |
Prefer constraints |
height |
double? |
null |
Prefer constraints |
constraints |
BoxConstraints? |
theme derived | Min height = Theme.buttonTheme.height minus border widths |
contentPadding |
EdgeInsetsGeometry? |
horiz:12, vert:4 |
|
margin |
EdgeInsetsGeometry? |
null |
|
alignment |
AlignmentGeometry? |
Alignment.center |
|
iconHeight |
double? |
content-based |
| Property | Type | Default |
|---|---|---|
animationDuration |
Duration? |
500ms |
animationAlignment |
AlignmentGeometry? |
Alignment.center |
statusShowingDuration |
Duration? |
2s |
roundBorderWhenRunning |
bool? |
true |
| Property | Type | Default |
|---|---|---|
onRunningWidget |
Widget? |
CircularProgressIndicator (themed) |
onSuccessWidget |
Widget? |
Icon(Icons.done) (themed) |
onErrorWidget |
Widget? |
Icon(Icons.error) (themed) |
showRunningStatusWidget |
bool? |
true |
| Property | Type | Default |
|---|---|---|
textAlign |
TextAlign? |
TextAlign.center |
textOverflow |
TextOverflow? |
TextOverflow.clip |
textHeightBehavior |
TextHeightBehavior? |
null |
textMaxLines |
int? |
null |
textWrap |
bool? |
true |
textWidthBasis |
TextWidthBasis? |
TextWidthBasis.parent |
| Property | Type | Default |
|---|---|---|
isRunning |
bool |
false |
splashFactory |
InteractiveInkFeatureFactory? |
null |
child |
Widget? |
SizedBox() |
Static class with nullable fields for all OnProcessButtonWidget properties. Set before runApp(). Values persist for the application lifetime β there is no reset() method.
OnProcessButtonDefaultValues.borderRadius = BorderRadius.circular(12);
OnProcessButtonDefaultValues.expandedIcon = true;
OnProcessButtonDefaultValues.roundBorderWhenRunning = false;Immutable theme data class holding all default values. Use with OnProcessButtonTheme.
const OnProcessButtonThemeData(
borderRadius: BorderRadius.all(Radius.circular(16)),
iconColor: Colors.white,
)Includes a copyWith() method for deriving modified instances:
final theme = const OnProcessButtonThemeData(iconColor: Colors.white);
final modified = theme.copyWith(iconColor: Colors.black);Inherited widget providing scoped defaults. Retrievable via OnProcessButtonTheme.of(context).
Convenience StatelessWidget wrapping OnProcessButtonTheme.
OnProcessButtonThemeProvider(
data: const OnProcessButtonThemeData(borderRadius: ...),
child: MyWidget(),
)BSD 3-Clause License β see LICENSE for details.
Made with β€οΈ by Shajedur Rahman Panna





