Button Widget
One enum-driven button — 5 types, solid/pill/outline styles, 4 sizes, icon + loading
View component →A single, reusable Flutter chip widget driven by enums. Covers four kinds — basic (filled or outlined), input (leading avatar or deletable ×), choice (with a lavender selected state), and status (success, warning, danger, info via a semantic palette) — all pill-shaped with hairline borders. Built on the FlutterKit house theme. No external packages, fully DartPad-ready.
What's Included
Use Cases
PRO TIP
Keep a single AppChip in your design system and switch its look with the kind and status enums instead of hand-rolling Chip, InputChip, ChoiceChip, and FilterChip separately. Wrap groups of chips in a Wrap widget with spacing: 8 and runSpacing: 8 so they reflow cleanly across screen widths, and drive the selected flag from your own state so a filter bar stays a single source of truth.
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: 8,
runSpacing: 8,
children: [
const AppChip(label: 'Basic'),
const AppChip(label: 'Outlined', outlined: true),
const AppChip(label: 'Flutter', icon: Icons.bolt),
const AppChip(label: 'Ada Lovelace', avatarText: 'AL'),
AppChip(label: 'Removable', onDeleted: () {}),
AppChip(
label: 'Selected',
kind: ChipKind.choice,
selected: true,
onTap: () {},
),
const AppChip(
label: 'Success',
kind: ChipKind.status,
status: ChipStatus.success,
),
const AppChip(
label: 'Warning',
kind: ChipKind.status,
status: ChipStatus.warning,
),
],
),
),
),
),
);
}
}
/// FlutterKit Chip — house design system (Linear lavender, light theme).
/// One enum-driven chip: basic, input, choice, and status kinds.
/// rohansurve.in/flutterkit/chip
enum ChipKind { basic, input, choice, status }
enum ChipStatus { success, warning, danger, info }
class AppChip extends StatelessWidget {
const AppChip({
super.key,
required this.label,
this.kind = ChipKind.basic,
this.outlined = false,
this.selected = false,
this.status = ChipStatus.info,
this.icon,
this.avatarText,
this.onTap,
this.onDeleted,
});
/// Chip text.
final String label;
/// basic · input · choice · status.
final ChipKind kind;
/// Basic/choice: transparent fill + hairline border instead of a muted fill.
final bool outlined;
/// Choice chip: lavender-tinted selected state.
final bool selected;
/// Status chip color (uses the semantic triplet).
final ChipStatus status;
/// Optional leading icon.
final IconData? icon;
/// Input chip: leading avatar with initials (e.g. 'AL').
final String? avatarText;
/// Makes the whole chip tappable (choice / filter chips).
final VoidCallback? onTap;
/// Shows a trailing × that calls this (input chips).
final VoidCallback? onDeleted;
// ── 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);
static const Color _surfaceMuted = Color(0xFFF4F5F7);
static const Color _hairline = Color(0xFFE6E8EB);
static const Color _hairlineStrong = Color(0xFFD5D8DD);
static const Color _ink = Color(0xFF1C1E26);
// Semantic triplets: [fg, bg, border].
static const Map<ChipStatus, List<Color>> _semantic = {
ChipStatus.success: [
Color(0xFF1F9D55),
Color(0xFFE7F6EE),
Color(0xFFBCE6CE),
],
ChipStatus.warning: [
Color(0xFFB7791F),
Color(0xFFFCF3E3),
Color(0xFFEBD9AE),
],
ChipStatus.danger: [
Color(0xFFD92D20),
Color(0xFFFDECEA),
Color(0xFFF4C7C1),
],
ChipStatus.info: [
Color(0xFF3E63DD),
Color(0xFFEAEFFC),
Color(0xFFC4D2F7),
],
};
@override
Widget build(BuildContext context) {
// Resolve fill / text / border from kind + state.
late final Color fill;
late final Color fg;
late final Color border;
switch (kind) {
case ChipKind.status:
final t = _semantic[status]!;
fg = t[0];
fill = t[1];
border = t[2];
break;
case ChipKind.choice:
if (selected) {
fg = _accent;
fill = _accentTint;
border = _accentBorder;
break;
}
fg = _ink;
fill = outlined ? Colors.transparent : _surfaceMuted;
border = outlined ? _hairlineStrong : _hairline;
break;
case ChipKind.basic:
case ChipKind.input:
fg = _ink;
fill = outlined ? Colors.transparent : _surfaceMuted;
border = outlined ? _hairlineStrong : _hairline;
break;
}
final children = <Widget>[];
if (avatarText != null) {
children
..add(Container(
width: 20,
height: 20,
alignment: Alignment.center,
decoration: const BoxDecoration(
color: _accent,
shape: BoxShape.circle,
),
child: Text(
avatarText!,
style: const TextStyle(
color: Colors.white,
fontSize: 10,
fontWeight: FontWeight.w600,
),
),
))
..add(const SizedBox(width: 6));
} else if (icon != null) {
children
..add(Icon(icon, size: 16, color: fg))
..add(const SizedBox(width: 6));
}
children.add(Text(
label,
style: TextStyle(
color: fg,
fontSize: 13,
fontWeight: FontWeight.w500,
height: 1.2,
),
));
if (onDeleted != null) {
children
..add(const SizedBox(width: 6))
..add(GestureDetector(
onTap: onDeleted,
child: Icon(Icons.close, size: 14, color: fg.withOpacity(0.7)),
));
}
final chip = Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
decoration: BoxDecoration(
color: fill,
borderRadius: BorderRadius.circular(999), // pill
border: Border.all(color: border),
),
child: Row(mainAxisSize: MainAxisSize.min, children: children),
);
if (onTap == null) return chip;
return Material(
color: Colors.transparent,
borderRadius: BorderRadius.circular(999),
child: InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(999),
child: chip,
),
);
}
}
// ── Usage ──
// const AppChip(label: 'Design') // basic filled
// const AppChip(label: 'Design', outlined: true) // basic outlined
// const AppChip(label: 'Flutter', icon: Icons.bolt) // leading icon
// const AppChip(label: 'Ada Lovelace', avatarText: 'AL') // input avatar
// AppChip(label: 'Removable', onDeleted: () {}) // deletable ×
// AppChip(label: 'All', kind: ChipKind.choice, selected: true, onTap: () {})
// const AppChip(label: 'Live', kind: ChipKind.status, status: ChipStatus.success)One enum-driven button — 5 types, solid/pill/outline styles, 4 sizes, icon + loading
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 →