UI ELEMENTS

Stat Card

A reusable Flutter Stat Card widget for dashboards and analytics screens. One StatelessWidget driven by a StatCardStyle enum gives you 4 layouts — Simple text card, leading-icon card, trend-percentage card, and circular-progress card — covering nearly every dashboard metric you will ever need to render.

statdashboardmetric
Preview16 variants

Total Revenue

$45,231.89

vs last month

Active Users

2,350

last 30 days

Conversions

12.5%

goal: 15%

New Orders

573

today

What's Included

  • Simple style — label and large value with optional description
  • With Icon style — colored icon tile beside the metric
  • With Trend style — green/red trend chip with up or down arrow
  • With Progress style — circular progress ring with percentage in the center
  • Auto-color trend chip — positive values turn green, negative turn red
  • Customizable accent color for icon tile and progress ring

Use Cases

  • Analytics dashboards — show revenue, active users, conversions, and orders
  • Admin panels — surface KPIs at a glance on the home screen
  • Finance apps — display account balances, savings goals, and spending breakdowns
  • Project trackers — show task completion percentages and milestone progress

PRO TIP

Pair StatCard with a GridView.count(crossAxisCount: 2) for a clean 2-column dashboard on mobile and crossAxisCount: 4 on tablets via LayoutBuilder. For animated number transitions on data refresh, wrap the value Text in a TweenAnimationBuilder<double> — it makes your dashboard feel alive without any extra packages.

Complete Dart Code

Copy this into your Flutter project. No external packages required.

dart
import 'package:flutter/material.dart';

/// Flutter Stat Card Widget — All Variants
/// Copy the StatCard class and switch layout via StatCardStyle.
/// rohansurve.in/flutterkit/stat-card

enum StatCardStyle { simple, withIcon, withTrend, withProgress }

class StatCard extends StatelessWidget {
  final StatCardStyle style;
  final String label;
  final String value;
  final String? description;
  final IconData? icon;
  final Color iconBg;
  final Color iconColor;
  final double? trend;
  final double? progress;
  final Color accentColor;

  const StatCard({
    super.key,
    required this.label,
    required this.value,
    this.style = StatCardStyle.simple,
    this.description,
    this.icon,
    this.iconBg = const Color(0xFFF3E8FF),
    this.iconColor = const Color(0xFF7C3AED),
    this.trend,
    this.progress,
    this.accentColor = const Color(0xFF7C3AED),
  });

  @override
  Widget build(BuildContext context) {
    switch (style) {
      case StatCardStyle.simple:
        return _buildSimple();
      case StatCardStyle.withIcon:
        return _buildWithIcon();
      case StatCardStyle.withTrend:
        return _buildWithTrend();
      case StatCardStyle.withProgress:
        return _buildWithProgress();
    }
  }

  Widget _shell({required Widget child}) {
    return Container(
      padding: const EdgeInsets.all(20),
      decoration: BoxDecoration(
        color: Colors.white,
        borderRadius: BorderRadius.circular(16),
        border: Border.all(color: const Color(0xFFEEEEEE)),
      ),
      child: child,
    );
  }

  Widget _buildSimple() {
    return _shell(
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text(label,
              style: const TextStyle(
                  fontSize: 13,
                  color: Color(0xFF888888),
                  fontWeight: FontWeight.w500)),
          const SizedBox(height: 8),
          Text(value,
              style: const TextStyle(
                  fontSize: 26,
                  fontWeight: FontWeight.w600,
                  color: Color(0xFF111111))),
          if (description != null) ...[
            const SizedBox(height: 6),
            Text(description!,
                style: const TextStyle(
                    fontSize: 12, color: Color(0xFFAAAAAA))),
          ],
        ],
      ),
    );
  }

  Widget _buildWithIcon() {
    return _shell(
      child: Row(
        children: [
          Container(
            width: 44,
            height: 44,
            decoration: BoxDecoration(
              color: iconBg,
              borderRadius: BorderRadius.circular(12),
            ),
            child: Icon(icon ?? Icons.analytics_rounded,
                color: iconColor, size: 22),
          ),
          const SizedBox(width: 14),
          Expanded(
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text(label,
                    style: const TextStyle(
                        fontSize: 12,
                        color: Color(0xFF888888),
                        fontWeight: FontWeight.w500)),
                const SizedBox(height: 4),
                Text(value,
                    style: const TextStyle(
                        fontSize: 20,
                        fontWeight: FontWeight.w600,
                        color: Color(0xFF111111))),
              ],
            ),
          ),
        ],
      ),
    );
  }

  Widget _buildWithTrend() {
    final isPositive = (trend ?? 0) >= 0;
    final trendColor = isPositive
        ? const Color(0xFF15803D)
        : const Color(0xFFBE123C);
    final trendBg = isPositive
        ? const Color(0xFFF0FDF4)
        : const Color(0xFFFFF1F2);

    return _shell(
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Row(
            mainAxisAlignment: MainAxisAlignment.spaceBetween,
            children: [
              Text(label,
                  style: const TextStyle(
                      fontSize: 13,
                      color: Color(0xFF888888),
                      fontWeight: FontWeight.w500)),
              if (trend != null)
                Container(
                  padding: const EdgeInsets.symmetric(
                      horizontal: 8, vertical: 3),
                  decoration: BoxDecoration(
                    color: trendBg,
                    borderRadius: BorderRadius.circular(20),
                  ),
                  child: Row(
                    mainAxisSize: MainAxisSize.min,
                    children: [
                      Icon(
                          isPositive
                              ? Icons.arrow_upward_rounded
                              : Icons.arrow_downward_rounded,
                          color: trendColor,
                          size: 12),
                      const SizedBox(width: 2),
                      Text('${trend!.abs().toStringAsFixed(1)}%',
                          style: TextStyle(
                              fontSize: 11,
                              fontWeight: FontWeight.w600,
                              color: trendColor)),
                    ],
                  ),
                ),
            ],
          ),
          const SizedBox(height: 8),
          Text(value,
              style: const TextStyle(
                  fontSize: 26,
                  fontWeight: FontWeight.w600,
                  color: Color(0xFF111111))),
        ],
      ),
    );
  }

  Widget _buildWithProgress() {
    final pct = (progress ?? 0).clamp(0.0, 1.0);
    return _shell(
      child: Row(
        children: [
          SizedBox(
            width: 56,
            height: 56,
            child: Stack(
              alignment: Alignment.center,
              children: [
                CircularProgressIndicator(
                  value: pct,
                  strokeWidth: 6,
                  backgroundColor: const Color(0xFFEDE9FE),
                  valueColor: AlwaysStoppedAnimation<Color>(accentColor),
                ),
                Text('${(pct * 100).round()}%',
                    style: const TextStyle(
                        fontSize: 11,
                        fontWeight: FontWeight.w600,
                        color: Color(0xFF111111))),
              ],
            ),
          ),
          const SizedBox(width: 14),
          Expanded(
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text(label,
                    style: const TextStyle(
                        fontSize: 12,
                        color: Color(0xFF888888),
                        fontWeight: FontWeight.w500)),
                const SizedBox(height: 4),
                Text(value,
                    style: const TextStyle(
                        fontSize: 20,
                        fontWeight: FontWeight.w600,
                        color: Color(0xFF111111))),
              ],
            ),
          ),
        ],
      ),
    );
  }
}

// ─── USAGE EXAMPLES ──────────────────────────────────────────────

// Simple
// StatCard(
//   label: 'Total Revenue',
//   value: '\$45,231.89',
//   description: 'vs last month',
// )

// With Icon
// StatCard(
//   style: StatCardStyle.withIcon,
//   label: 'Active Users',
//   value: '2,350',
//   icon: Icons.people_outline_rounded,
// )

// With Trend
// StatCard(
//   style: StatCardStyle.withTrend,
//   label: 'Conversions',
//   value: '12.5%',
//   trend: 4.2,
// )

// With Progress
// StatCard(
//   style: StatCardStyle.withProgress,
//   label: 'Audits Complete',
//   value: '\$12,540',
//   progress: 0.5,
// )

How to use this widget

  1. 1Copy the Dart code above
  2. 2Create a new file — stat_card.dart in your Flutter project
  3. 3Use StatCard(label: 'Revenue', value: '\$45k') — change the style parameter to switch between Simple, Icon, Trend, and Progress layouts

Related components

UI Elements

Alert Widget

Dismissible alert banners in 5 styles — Info, Success, Warning, and Danger

View component →
E-commerce

Course Detail Page

Course detail with hero image, mentor, tabs, lessons and sticky enroll bar

View component →
E-commerce

Grocery Home Screen

Grocery home with brand header, search, banner, product cards and rounded bottom nav

View component →