AVATARS & CHIPS

Badge

A single, reusable Flutter badge widget driven by enums. Covers four kinds — count (a number bubble anchored to an icon, capped as 99+), dot (a small unread indicator), label (a lavender NEW / PRO tag), and status (success, warning, danger, info from a semantic palette). Count and dot kinds anchor to any child icon or avatar with a Stack overlay; label and status render as standalone pills. Built on the FlutterKit house theme, no external packages, fully DartPad-ready.

badgecountnotification
Preview11 variants

Notifications

3

Capped 99+

99+

What's Included

  • Count badges — a number bubble anchored to an icon, capped as 99+
  • Dot badges — a small unread / presence indicator with a white ring
  • Label badges — a lavender-tinted NEW / PRO / BETA tag
  • Status badges — success, warning, danger, info from a semantic triplet palette
  • Count + dot kinds anchor to any icon or avatar via a Stack overlay
  • One AppBadge widget driven entirely by BadgeKind and BadgeStatus enums

Use Cases

  • Notification icons — a count bubble on a bell or inbox showing unread totals
  • Presence dots — an online / unread indicator on an avatar or tab
  • New-feature tags — a lavender NEW or PRO label beside a menu item
  • Inline status — success / warning / danger / info pills in a list row

PRO TIP

Reach for Flutter's built-in Badge widget only for the simplest cases — a custom AppBadge lets you keep the count cap, white ring, and house colors consistent everywhere. Add a white border around count and dot badges so they read clearly when they overlap a busy icon, and animate the count with an AnimatedSwitcher so increments feel alive instead of popping in.

Complete Dart Code

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

dart
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: const Color(0xFFFAFAFB),
        body: Center(
          child: Padding(
            padding: const EdgeInsets.all(24),
            child: Wrap(
              spacing: 28,
              runSpacing: 24,
              crossAxisAlignment: WrapCrossAlignment.center,
              children: [
                const AppBadge(
                  kind: BadgeKind.count,
                  count: 3,
                  child: Icon(Icons.notifications_outlined,
                      size: 28, color: Color(0xFF1C1E26)),
                ),
                const AppBadge(
                  kind: BadgeKind.count,
                  count: 128,
                  child: Icon(Icons.mail_outline,
                      size: 28, color: Color(0xFF1C1E26)),
                ),
                const AppBadge(
                  kind: BadgeKind.dot,
                  child: Icon(Icons.person_outline,
                      size: 28, color: Color(0xFF1C1E26)),
                ),
                const AppBadge(kind: BadgeKind.label, label: 'NEW'),
                const AppBadge(
                  kind: BadgeKind.status,
                  label: 'Live',
                  status: BadgeStatus.success,
                ),
                const AppBadge(
                  kind: BadgeKind.status,
                  label: 'Failed',
                  status: BadgeStatus.danger,
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }
}

/// FlutterKit Badge — house design system (Linear lavender, light theme).
/// One enum-driven badge: count, dot, label, and status kinds.
/// rohansurve.in/flutterkit/badge

enum BadgeKind { count, dot, label, status }

enum BadgeStatus { success, warning, danger, info }

class AppBadge extends StatelessWidget {
  const AppBadge({
    super.key,
    this.kind = BadgeKind.label,
    this.label,
    this.count,
    this.maxCount = 99,
    this.status = BadgeStatus.info,
    this.child,
  });

  /// count · dot · label · status.
  final BadgeKind kind;

  /// Text for the label / status kinds.
  final String? label;

  /// Number shown by the count kind.
  final int? count;

  /// Count cap before the badge shows '99+'.
  final int maxCount;

  /// Color for the status kind (semantic triplet).
  final BadgeStatus status;

  /// Icon or avatar the count / dot badge is anchored on.
  final Widget? child;

  // ── House tokens — docs/flutterkit-design.md §1 ──
  static const Color _accent = Color(0xFF5E6AD2);
  static const Color _accentTint = Color(0xFFEEF0FB);
  static const Color _accentBorder = Color(0xFFD5D9F4);

  // Semantic triplets: [fg, bg, border].
  static const Map<BadgeStatus, List<Color>> _semantic = {
    BadgeStatus.success: [
      Color(0xFF1F9D55),
      Color(0xFFE7F6EE),
      Color(0xFFBCE6CE),
    ],
    BadgeStatus.warning: [
      Color(0xFFB7791F),
      Color(0xFFFCF3E3),
      Color(0xFFEBD9AE),
    ],
    BadgeStatus.danger: [
      Color(0xFFD92D20),
      Color(0xFFFDECEA),
      Color(0xFFF4C7C1),
    ],
    BadgeStatus.info: [
      Color(0xFF3E63DD),
      Color(0xFFEAEFFC),
      Color(0xFFC4D2F7),
    ],
  };

  @override
  Widget build(BuildContext context) {
    switch (kind) {
      case BadgeKind.label:
        return _pill(
          text: label ?? '',
          fg: _accent,
          fill: _accentTint,
          border: _accentBorder,
        );
      case BadgeKind.status:
        final t = _semantic[status]!;
        return _pill(text: label ?? '', fg: t[0], fill: t[1], border: t[2]);
      case BadgeKind.count:
        return _anchored(_countBadge());
      case BadgeKind.dot:
        return _anchored(_dotBadge());
    }
  }

  // Standalone text pill — label / status kinds.
  Widget _pill({
    required String text,
    required Color fg,
    required Color fill,
    required Color border,
  }) {
    return Container(
      padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
      decoration: BoxDecoration(
        color: fill,
        borderRadius: BorderRadius.circular(999), // pill
        border: Border.all(color: border),
      ),
      child: Text(
        text,
        style: TextStyle(
          color: fg,
          fontSize: 11,
          fontWeight: FontWeight.w600,
          letterSpacing: 0.4,
          height: 1.2,
        ),
      ),
    );
  }

  // Solid count bubble — circle for one digit, pill once it widens.
  Widget _countBadge() {
    final n = count ?? 0;
    final text = n > maxCount ? '$maxCount+' : '$n';
    return Container(
      constraints: const BoxConstraints(minWidth: 18),
      height: 18,
      padding: const EdgeInsets.symmetric(horizontal: 5),
      alignment: Alignment.center,
      decoration: BoxDecoration(
        color: _accent,
        borderRadius: BorderRadius.circular(999),
        border: Border.all(color: Colors.white, width: 1.5),
      ),
      child: Text(
        text,
        style: const TextStyle(
          color: Colors.white,
          fontSize: 10,
          fontWeight: FontWeight.w700,
          height: 1.0,
        ),
      ),
    );
  }

  // Small presence / unread dot with a white ring.
  Widget _dotBadge() {
    return Container(
      width: 10,
      height: 10,
      decoration: BoxDecoration(
        color: _accent,
        shape: BoxShape.circle,
        border: Border.all(color: Colors.white, width: 1.5),
      ),
    );
  }

  // Anchor a count / dot badge at the top-right of the child.
  Widget _anchored(Widget badge) {
    if (child == null) return badge;
    return Stack(
      clipBehavior: Clip.none,
      children: [
        child!,
        Positioned(right: -6, top: -6, child: badge),
      ],
    );
  }
}

// ── Usage ──
// const AppBadge(kind: BadgeKind.count, count: 3, child: Icon(Icons.notifications))
// const AppBadge(kind: BadgeKind.dot, child: Icon(Icons.person))
// const AppBadge(kind: BadgeKind.label, label: 'NEW')
// const AppBadge(kind: BadgeKind.status, label: 'Live', status: BadgeStatus.success)

How to use this widget

  1. 1Copy the complete Dart code above
  2. 2Create a new file — app_badge.dart in your Flutter project
  3. 3Use AppBadge(kind: BadgeKind.count, count: 3, child: Icon(Icons.notifications)) — switch the look with the kind, status, count, and child parameters

Related components

Avatars & Chips

Chip

One enum-driven chip — basic, input, choice & status kinds, lavender selected state

View component →
Feedback & Alerts

Alert Widget

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

View component →
Data Display

Stat Card

Dashboard metric cards in 4 styles — Simple, Icon, Trend, and Progress

View component →