SELECTION CONTROLS

Segmented Control

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.

segmentedtoggletabs
Preview6 variants

Day

DayWeekMonth

Week

DayWeekMonth

What's Included

  • Exactly one selected segment, lifting to a white pill
  • Animated 160ms selection move with a soft shadow
  • Lavender label + icon on the selected segment
  • Optional leading icon per segment via AppSegment
  • Generic value type — back it with an enum or any value
  • Disabled look — the whole control dims, taps ignored

Use Cases

  • Day / Week / Month view switchers on a calendar
  • List vs grid layout toggles on a browse screen
  • Two- or three-way filters above a feed
  • Compact tab bars where a full TabBar is overkill

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.

Complete Dart Code

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

dart
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)                         // disabled

How to use this widget

  1. 1Copy the complete Dart code above
  2. 2Create a new file — app_segmented_control.dart in your Flutter project
  3. 3Use AppSegmentedControl<ViewMode>(value: v, segments: const [...], onChanged: (n) {}) — switch the look with the segments, icons, and enabled parameters

Related components

Selection Controls

Choice Chips

Single-select chip group — lavender selected chip, pill shape, wraps across lines

View component →
Selection Controls

Switch Toggle

Animated on/off switch — lavender track, two sizes, with label and disabled states

View component →
Buttons & Actions

Button Widget

One enum-driven button — 5 types, solid/pill/outline styles, 4 sizes, icon + loading

View component →