Alert Widget
Dismissible alert banners in 5 styles — Info, Success, Warning, and Danger
View component →A single, reusable Flutter button widget driven entirely by enums. Switch between 5 types (Primary, Secondary, Success, Danger, Warning), 3 styles (Solid, Pill, Outline), and 4 sizes with one parameter each — plus built-in leading icon and loading spinner support. No external packages, fully DartPad-ready.
What's Included
Use Cases
PRO TIP
Keep a single AppButton in your design system instead of scattering ElevatedButton, OutlinedButton, and TextButton with ad-hoc styles. Drive every look through the type, variant, and size enums — when your brand color changes, you edit one constant and every button in the app updates. Always pass isLoading during async work so users cannot double-tap and fire a request twice.
Copy this into your Flutter project. No external packages required.
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
backgroundColor: Colors.white,
body: Center(
child: AppButton(
label: 'Buy now',
icon: Icons.shopping_cart_outlined,
onPressed: () {},
),
),
),
);
}
}
/// Flutter Button Widget — All Variants
/// One enum-driven button: 5 types, 3 styles, 4 sizes, icon + loading.
/// rohansurve.in/flutterkit/button-widget
enum ButtonType { primary, secondary, success, danger, warning }
enum ButtonVariant { solid, pill, outline }
enum ButtonSize { xs, sm, lg, xl }
class AppButton extends StatelessWidget {
final String label;
final VoidCallback? onPressed;
final ButtonType type;
final ButtonVariant variant;
final ButtonSize size;
final IconData? icon;
final bool isLoading;
const AppButton({
super.key,
required this.label,
this.onPressed,
this.type = ButtonType.primary,
this.variant = ButtonVariant.solid,
this.size = ButtonSize.sm,
this.icon,
this.isLoading = false,
});
static const Color _brand = Color(0xFF1D9E75);
static const Color _success = Color(0xFF22C55E);
static const Color _danger = Color(0xFFEF4444);
static const Color _warning = Color(0xFFF59E0B);
static const Color _secondaryBg = Color(0xFFF1F5F9);
static const Color _secondaryText = Color(0xFF334155);
static const Color _secondaryBorder = Color(0xFFE2E8F0);
Color get _baseColor {
switch (type) {
case ButtonType.primary:
return _brand;
case ButtonType.secondary:
return _secondaryBg;
case ButtonType.success:
return _success;
case ButtonType.danger:
return _danger;
case ButtonType.warning:
return _warning;
}
}
double get _fontSize {
switch (size) {
case ButtonSize.xs:
return 12;
case ButtonSize.sm:
return 14;
case ButtonSize.lg:
return 16;
case ButtonSize.xl:
return 20;
}
}
EdgeInsets get _padding {
switch (size) {
case ButtonSize.xs:
return const EdgeInsets.symmetric(horizontal: 12, vertical: 8);
case ButtonSize.sm:
return const EdgeInsets.symmetric(horizontal: 16, vertical: 10);
case ButtonSize.lg:
return const EdgeInsets.symmetric(horizontal: 20, vertical: 12);
case ButtonSize.xl:
return const EdgeInsets.symmetric(horizontal: 24, vertical: 14);
}
}
@override
Widget build(BuildContext context) {
final bool isOutline = variant == ButtonVariant.outline;
final bool isSecondary = type == ButtonType.secondary;
final double radius = variant == ButtonVariant.pill ? 999 : 8;
final Color bg = isOutline ? Colors.transparent : _baseColor;
final Color fg = isOutline
? _baseColor
: isSecondary
? _secondaryText
: Colors.white;
final Color borderColor = isOutline
? _baseColor
: isSecondary
? _secondaryBorder
: Colors.transparent;
return Opacity(
opacity: isLoading ? 0.75 : 1,
child: Material(
color: bg,
borderRadius: BorderRadius.circular(radius),
child: InkWell(
onTap: isLoading ? null : onPressed,
borderRadius: BorderRadius.circular(radius),
child: Container(
padding: _padding,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(radius),
border: Border.all(color: borderColor),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
if (isLoading) ...[
SizedBox(
width: _fontSize,
height: _fontSize,
child: CircularProgressIndicator(strokeWidth: 2, color: fg),
),
const SizedBox(width: 8),
] else if (icon != null) ...[
Icon(icon, size: _fontSize + 2, color: fg),
const SizedBox(width: 8),
],
Text(
label,
style: TextStyle(
color: fg,
fontSize: _fontSize,
fontWeight: FontWeight.w600,
),
),
],
),
),
),
),
);
}
}
// ─── USAGE EXAMPLES ──────────────────────────────────────────────
// Primary solid (default)
// AppButton(label: 'Default', onPressed: () {})
// Secondary solid
// AppButton(label: 'Secondary', type: ButtonType.secondary, onPressed: () {})
// Danger pill
// AppButton(label: 'Delete', type: ButtonType.danger, variant: ButtonVariant.pill, onPressed: () {})
// Success outline
// AppButton(label: 'Approve', type: ButtonType.success, variant: ButtonVariant.outline, onPressed: () {})
// Extra large with icon
// AppButton(label: 'Buy now', size: ButtonSize.xl, icon: Icons.shopping_cart_outlined, onPressed: () {})
// Loading state
// AppButton(label: 'Loading...', isLoading: true, onPressed: () {})Dismissible alert banners in 5 styles — Info, Success, Warning, and Danger
View component →Clean email + password login with form validation and loading state
View component →Dashboard metric cards in 4 styles — Simple, Icon, Trend, and Progress
View component →