Choice Chips
Single-select chip group — lavender selected chip, pill shape, wraps across lines
View component →A single, reusable Flutter segmented control driven by a generic list of segments. One option is always selected — it lifts to a white pill with a soft shadow and lavender label, animating as the selection moves, over a muted-grey track. Segments take an optional leading icon, and the whole control supports a disabled look. No external packages, fully DartPad-ready.
What's Included
Use Cases
PRO TIP
Keep segmented controls to two to four short options — past that, the labels cramp and a dropdown or tabs read better. Always keep exactly one segment selected (there is no empty state), and back the value with an enum rather than an index so reordering segments never silently changes what is selected.
Copy this into your Flutter project. No external packages required.
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
enum ViewMode { day, week, month }
class MyApp extends StatefulWidget {
const MyApp({super.key});
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
ViewMode _mode = ViewMode.week;
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
backgroundColor: const Color(0xFFFAFAFB),
body: Center(
child: AppSegmentedControl<ViewMode>(
value: _mode,
onChanged: (v) => setState(() => _mode = v),
segments: const [
AppSegment(value: ViewMode.day, label: 'Day'),
AppSegment(value: ViewMode.week, label: 'Week'),
AppSegment(value: ViewMode.month, label: 'Month'),
],
),
),
),
);
}
}
/// FlutterKit Segmented Control — house design system (Linear lavender, light).
/// One-of-N selection; the chosen segment lifts to a white pill.
/// rohansurve.in/flutterkit/segmented-control
class AppSegment<T> {
const AppSegment({required this.value, required this.label, this.icon});
/// The value this segment selects.
final T value;
/// Segment label.
final String label;
/// Optional leading icon.
final IconData? icon;
}
class AppSegmentedControl<T> extends StatelessWidget {
const AppSegmentedControl({
super.key,
required this.segments,
required this.value,
this.onChanged,
this.enabled = true,
});
/// The selectable segments.
final List<AppSegment<T>> segments;
/// Currently selected value.
final T value;
/// Called with the next value on tap.
final ValueChanged<T>? onChanged;
/// Disabled look — the whole control dims, taps ignored.
final bool enabled;
// ── House tokens — docs/flutterkit-design.md §1 ──
static const Color _accent = Color(0xFF5E6AD2);
static const Color _surfaceMuted = Color(0xFFF4F5F7);
static const Color _inkMuted = Color(0xFF51555E);
@override
Widget build(BuildContext context) {
return Opacity(
opacity: enabled ? 1 : 0.5,
child: Container(
padding: const EdgeInsets.all(4),
decoration: BoxDecoration(
color: _surfaceMuted,
borderRadius: BorderRadius.circular(10),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: segments.map((s) {
final selected = s.value == value;
return GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: enabled && onChanged != null
? () => onChanged!(s.value)
: null,
child: AnimatedContainer(
duration: const Duration(milliseconds: 160),
curve: Curves.easeInOut,
padding:
const EdgeInsets.symmetric(horizontal: 18, vertical: 9),
decoration: BoxDecoration(
color: selected ? Colors.white : Colors.transparent,
borderRadius: BorderRadius.circular(8),
boxShadow: selected
? const [
BoxShadow(
color: Color(0x14000000),
blurRadius: 4,
offset: Offset(0, 1),
),
]
: null,
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
if (s.icon != null) ...[
Icon(
s.icon,
size: 16,
color: selected ? _accent : _inkMuted,
),
const SizedBox(width: 6),
],
Text(
s.label,
style: TextStyle(
color: selected ? _accent : _inkMuted,
fontSize: 14,
fontWeight:
selected ? FontWeight.w600 : FontWeight.w500,
),
),
],
),
),
);
}).toList(),
),
),
);
}
}
// ── Usage ──
// AppSegmentedControl<ViewMode>(value: v, segments: const [...], onChanged: (n){})
// AppSegment(value: ViewMode.day, label: 'Day') // text segment
// AppSegment(value: ViewMode.day, label: 'Day', icon: Icons.today) // with icon
// AppSegmentedControl(..., enabled: false) // disabledSingle-select chip group — lavender selected chip, pill shape, wraps across lines
View component →Animated on/off switch — lavender track, two sizes, with label and disabled states
View component →One enum-driven button — 5 types, solid/pill/outline styles, 4 sizes, icon + loading
View component →