Stat Card
Dashboard metric cards in 4 styles — Simple, Icon, Trend, and Progress
View component →A polished wallet hero card inspired by modern finance apps. Combines a currency switcher chip, a prominent total-balance figure, and a row of quick-action tiles (Transfer, Top Up, Exchange, Pay Later) on top of a clean white surface. Drop it on your home screen and wire the actions to your routes.
Default (IDR)
IDR currency with 4 quick actions
USD
Alternate currency chip
Gradient
Brand-blue gradient background
Compact
Without the More features row
What's Included
Use Cases
PRO TIP
Reach for AnimatedSwitcher around the balance Text so balance updates fade-cross when you swap currency or refresh from the API. Render the formatted balance with NumberFormat.currency from the intl package so locale-aware separators (61.246.180 in id-ID vs 61,246,180 in en-US) come for free.
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: BalanceHero(),
),
),
);
}
}
/// Flutter Balance Hero Widget — All Variants
/// Wallet hero card with currency selector + quick actions.
/// rohansurve.in/flutterkit/balance-hero
enum BalanceHeroStyle { standard, gradient, compact }
class BalanceAction {
final IconData icon;
final String label;
final VoidCallback? onTap;
const BalanceAction({
required this.icon,
required this.label,
this.onTap,
});
}
class BalanceHero extends StatelessWidget {
final BalanceHeroStyle style;
final String currencyFlag;
final String currencyCode;
final String balanceLabel;
final String balance;
final List<BalanceAction> actions;
final String moreLabel;
final VoidCallback? onCurrencyTap;
final VoidCallback? onMoreTap;
const BalanceHero({
super.key,
this.style = BalanceHeroStyle.standard,
this.currencyFlag = '🇮🇩',
this.currencyCode = 'IDR',
this.balanceLabel = 'Total balance',
this.balance = '61.246.180',
this.moreLabel = 'More features',
this.onCurrencyTap,
this.onMoreTap,
this.actions = const [
BalanceAction(icon: Icons.arrow_upward_rounded, label: 'Transfer'),
BalanceAction(icon: Icons.arrow_downward_rounded, label: 'Top Up'),
BalanceAction(icon: Icons.sync_alt_rounded, label: 'Exchange'),
BalanceAction(icon: Icons.credit_card_rounded, label: 'Pay Later'),
],
});
static const _accent = Color(0xFF2563EB);
static const _gray900 = Color(0xFF111827);
static const _gray500 = Color(0xFF6B7280);
static const _gray200 = Color(0xFFE5E7EB);
static const _gray100 = Color(0xFFF3F4F6);
bool get _isGradient => style == BalanceHeroStyle.gradient;
Color get _textColor => _isGradient ? Colors.white : _gray900;
Color get _mutedColor =>
_isGradient ? Colors.white.withValues(alpha: 0.7) : _gray500;
Color get _chipBg => _isGradient ? Colors.white.withValues(alpha: 0.15) : Colors.white;
Color get _chipBorder => _isGradient ? Colors.transparent : _gray200;
Color get _actionsBg => _isGradient ? Colors.white.withValues(alpha: 0.1) : Colors.white;
Color get _actionsBorder => _isGradient ? Colors.transparent : _gray200;
@override
Widget build(BuildContext context) {
return Container(
width: 340,
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: _isGradient ? null : Colors.white,
gradient: _isGradient
? const LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [Color(0xFF2563EB), Color(0xFF1E3A8A)],
)
: null,
borderRadius: BorderRadius.circular(16),
border: Border.all(color: _isGradient ? Colors.transparent : _gray200),
boxShadow: const [
BoxShadow(
color: Color(0x0A000000),
blurRadius: 40,
),
],
),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
_buildHeader(),
const SizedBox(height: 20),
_buildActions(),
],
),
);
}
Widget _buildHeader() {
return Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
_currencyChip(),
const Spacer(),
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(balanceLabel,
style: TextStyle(
fontSize: 12, color: _mutedColor)),
const SizedBox(height: 4),
Text(balance,
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.w500,
color: _textColor)),
],
),
],
);
}
Widget _currencyChip() {
return InkWell(
onTap: onCurrencyTap,
borderRadius: BorderRadius.circular(80),
child: Container(
height: 36,
padding: const EdgeInsets.fromLTRB(8, 0, 10, 0),
decoration: BoxDecoration(
color: _chipBg,
borderRadius: BorderRadius.circular(80),
border: Border.all(color: _chipBorder),
),
child: Row(
children: [
Container(
width: 20,
height: 20,
alignment: Alignment.center,
decoration: BoxDecoration(
color: _isGradient
? Colors.white.withValues(alpha: 0.2)
: _gray100,
borderRadius: BorderRadius.circular(80),
),
child: Text(currencyFlag,
style: const TextStyle(fontSize: 12)),
),
const SizedBox(width: 8),
Text(currencyCode,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: _textColor)),
const SizedBox(width: 8),
Icon(Icons.keyboard_arrow_down_rounded,
size: 16, color: _textColor),
],
),
),
);
}
Widget _buildActions() {
return Container(
decoration: BoxDecoration(
color: _actionsBg,
borderRadius: BorderRadius.circular(16),
border: Border.all(color: _actionsBorder),
),
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Column(
children: [
Container(
padding: const EdgeInsets.symmetric(vertical: 20),
decoration: BoxDecoration(
border: style == BalanceHeroStyle.compact
? null
: Border(
bottom: BorderSide(
color: _isGradient
? Colors.white.withValues(alpha: 0.15)
: _gray200)),
),
child: Row(
children: [
for (final a in actions)
Expanded(
child: _actionItem(a),
),
],
),
),
if (style != BalanceHeroStyle.compact)
InkWell(
onTap: onMoreTap,
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 12),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(moreLabel,
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w500,
color: _textColor)),
const SizedBox(width: 6),
Icon(Icons.keyboard_arrow_down_rounded,
size: 12, color: _textColor),
],
),
),
),
],
),
);
}
Widget _actionItem(BalanceAction a) {
return InkWell(
onTap: a.onTap,
child: Column(
children: [
Container(
width: 40,
height: 40,
decoration: BoxDecoration(
color: _isGradient ? Colors.white : _accent,
borderRadius: BorderRadius.circular(80),
),
child: Icon(a.icon,
color: _isGradient ? _accent : Colors.white, size: 20),
),
const SizedBox(height: 8),
Text(a.label,
style: TextStyle(fontSize: 12, color: _textColor)),
],
),
);
}
}
// ─── USAGE ───────────────────────────────────────────────────────
// Default (IDR)
// BalanceHero()
// USD variant
// BalanceHero(
// currencyFlag: '🇺🇸',
// currencyCode: 'USD',
// balance: '4,128.50',
// )
// Gradient background
// BalanceHero(
// style: BalanceHeroStyle.gradient,
// )
// Compact (no More features)
// BalanceHero(
// style: BalanceHeroStyle.compact,
// )Dashboard metric cards in 4 styles — Simple, Icon, Trend, and Progress
View component →Payment QR + barcode card with header, tabs, and security footer
View component →Dismissible alert banners in 5 styles — Info, Success, Warning, and Danger
View component →