SELECTION CONTROLS

Choice Chips

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.

chipschoiceselect
Preview5 variants

Basic

BasicProTeam

Pro

BasicProTeam

Team

BasicProTeam

What's Included

  • Single selection across a group of pill-shaped chips
  • Lavender-tinted selected chip with a matching border
  • Muted-grey unselected chips with a hairline border
  • Wrap layout that reflows chips across lines
  • Generic value type — back it with an enum or any value
  • One AppChoiceChips widget driven by a value + onChanged

Use Cases

  • Plan or tier pickers — Basic / Pro / Team
  • Single-answer survey or quiz options
  • Sort-order selectors above a list
  • Category pickers where only one applies

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.

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

How to use this widget

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

Related components

Selection Controls

Filter Chips

Multi-select chip group — lavender check on selected chips, pill shape, wraps freely

View component →
Avatars & Chips

Chip

One enum-driven chip — basic, input, choice & status kinds, lavender selected state

View component →
Selection Controls

Segmented Control

iOS-style segmented control — sliding white selection on a muted track, icons & disabled

View component →