Filter Chips
Multi-select chip group — lavender check on selected chips, pill shape, wraps freely
View component →A single, reusable Flutter choice-chip group driven by a generic list of options. Exactly one chip is selected at a time — it fills with a lavender tint, lavender text, and a lavender border, while the rest stay muted-grey. The chips are pill-shaped, sized for touch, and wrap cleanly across lines. No external packages, fully DartPad-ready.
What's Included
Use Cases
PRO TIP
Choice chips are radio buttons in disguise — use them when exactly one option must be chosen and the labels are short. If users should pick several, reach for filter chips instead; if there are only two or three options that fit on one line, a segmented control reads tighter. Back the selection with an enum so it 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());
}
enum Plan { basic, pro, team }
class MyApp extends StatefulWidget {
const MyApp({super.key});
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
Plan _plan = Plan.pro;
@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: AppChoiceChips<Plan>(
value: _plan,
onChanged: (v) => setState(() => _plan = v),
options: const [
ChipOption(value: Plan.basic, label: 'Basic'),
ChipOption(value: Plan.pro, label: 'Pro'),
ChipOption(value: Plan.team, label: 'Team'),
],
),
),
),
),
);
}
}
/// FlutterKit Choice Chips — house design system (Linear lavender, light).
/// Single-select chip group; one chip is always chosen.
/// rohansurve.in/flutterkit/choice-chips
class ChipOption<T> {
const ChipOption({required this.value, required this.label});
/// The value this chip selects.
final T value;
/// Chip label.
final String label;
}
class AppChoiceChips<T> extends StatelessWidget {
const AppChoiceChips({
super.key,
required this.options,
required this.value,
this.onChanged,
this.enabled = true,
});
/// The selectable options.
final List<ChipOption<T>> options;
/// Currently selected value.
final T value;
/// Called with the next value on tap.
final ValueChanged<T>? onChanged;
/// Disabled look — taps ignored.
final bool enabled;
// ── 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 _ink = Color(0xFF1C1E26);
@override
Widget build(BuildContext context) {
return Wrap(
spacing: 8,
runSpacing: 8,
children: options.map((o) {
final selected = o.value == value;
final fg = selected ? _accent : _ink;
final fill = selected ? _accentTint : _surfaceMuted;
final border = selected ? _accentBorder : _hairline;
return Opacity(
opacity: enabled ? 1 : 0.5,
child: Material(
color: Colors.transparent,
child: InkWell(
borderRadius: BorderRadius.circular(999),
onTap: enabled && onChanged != null
? () => onChanged!(o.value)
: null,
child: Container(
padding:
const EdgeInsets.symmetric(horizontal: 14, vertical: 8),
decoration: BoxDecoration(
color: fill,
borderRadius: BorderRadius.circular(999),
border: Border.all(color: border),
),
child: Text(
o.label,
style: TextStyle(
color: fg,
fontSize: 13,
fontWeight: FontWeight.w500,
height: 1.2,
),
),
),
),
),
);
}).toList(),
);
}
}
// ── Usage ──
// AppChoiceChips<Plan>(value: v, options: const [...], onChanged: (n) {})
// ChipOption(value: Plan.pro, label: 'Pro') // one option
// AppChoiceChips(..., enabled: false) // disabledMulti-select chip group — lavender check on selected chips, pill shape, wraps freely
View component →One enum-driven chip — basic, input, choice & status kinds, lavender selected state
View component →iOS-style segmented control — sliding white selection on a muted track, icons & disabled
View component →