Chip
One enum-driven chip — basic, input, choice & status kinds, lavender selected state
View component →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.
What's Included
Use Cases
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.
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: 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)One enum-driven chip — basic, input, choice & status kinds, lavender selected state
View component →Dismissible alert banners in 5 styles — Info, Success, Warning, and Danger
View component →Dashboard metric cards in 4 styles — Simple, Icon, Trend, and Progress
View component →