smooth_ios_style_toggle 0.2.0 copy "smooth_ios_style_toggle: ^0.2.0" to clipboard
smooth_ios_style_toggle: ^0.2.0 copied to clipboard

A smooth, fully customizable iOS-style toggle with a pill thumb (~1.7× height), adaptive labels beside or inside the thumb, drag gestures, validated sizing, and built-in Flutter state management (Valu [...]

example/lib/main.dart

import 'package:flutter/material.dart';
import 'package:smooth_ios_style_toggle/smooth_ios_style_toggle.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Smooth iOS Toggle Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.teal),
        useMaterial3: true,
      ),
      darkTheme: ThemeData(
        brightness: Brightness.dark,
        colorScheme: ColorScheme.fromSeed(
          seedColor: Colors.teal,
          brightness: Brightness.dark,
        ),
        useMaterial3: true,
      ),
      home: const DemoPage(),
    );
  }
}

class DemoPage extends StatefulWidget {
  const DemoPage({super.key});

  @override
  State<DemoPage> createState() => _DemoPageState();
}

class _DemoPageState extends State<DemoPage> {
  // setState-controlled
  bool _notifications = true;
  bool _darkMode = false;
  bool _compact = false;

  // Controller
  final SmoothToggleController _wifiController =
      SmoothToggleController(value: true);

  // Large toggle
  bool _premium = false;

  @override
  void dispose() {
    _wifiController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Smooth iOS Toggle v0.2.0'),
      ),
      body: SmoothToggleTheme(
        data: const SmoothToggleStyle(
          trackHeight: 34,
          trackWidth: 110,
          animationDuration: Duration(milliseconds: 240),
        ),
        child: ListView(
          padding: const EdgeInsets.fromLTRB(20, 12, 20, 32),
          children: [
            _IntroBanner(),
            const SizedBox(height: 20),
            _Section(
              title: '1 · setState + custom style',
              subtitle:
                  'thumbWidth, long labels, adaptive text, semanticLabel, onToggle',
              child: _DemoRow(
                label: _notifications ? 'Online' : 'Offline',
                child: SmoothIOSToggle(
                  value: _notifications,
                  activeText: 'Online',
                  inactiveText: 'Offline',
                  hapticFeedback: true,
                  onChanged: (v) => setState(() => _notifications = v),
                  onToggle: () => debugPrint('Notifications toggled'),
                  style: SmoothToggleStyle(
                    trackWidth: 120,
                    trackHeight: 36,
                    thumbWidth: 48,
                    trackPadding: 4,
                    activeTrackColor: Colors.deepPurple,
                    inactiveTrackColor: Colors.grey.shade700,
                    activeThumbColor: Colors.white,
                    inactiveThumbColor: Colors.white,
                    activeTextColor: Colors.white,
                    inactiveTextColor: Colors.white70,
                    semanticLabel: 'Notifications',
                  ),
                ),
              ),
            ),
            _Section(
              title: '2 · SmoothToggleController',
              subtitle: 'ValueNotifier + ValueListenableBuilder',
              child: _DemoRow(
                label: _wifiController.value ? 'Connected' : 'Disconnected',
                child: ValueListenableBuilder<bool>(
                  valueListenable: _wifiController,
                  builder: (context, value, _) {
                    return SmoothIOSToggle(
                      value: value,
                      controller: _wifiController,
                      activeText: 'Yes',
                      inactiveText: 'No',
                      onChanged: (v) => _wifiController.value = v,
                    );
                  },
                ),
              ),
            ),
            _Section(
              title: '3 · Text inside thumb',
              subtitle: 'textInsideThumb + fitted label sizing',
              child: _DemoRow(
                label: _darkMode ? 'Dark' : 'Lite',
                child: SmoothIOSToggle(
                  value: _darkMode,
                  textInsideThumb: true,
                  activeText: 'Dark',
                  inactiveText: 'Lite',
                  onChanged: (v) => setState(() => _darkMode = v),
                  style: SmoothToggleStyle(
                    trackWidth: 116,
                    trackHeight: 36,
                    activeTrackColor: Colors.deepPurple,
                    inactiveTrackColor: Colors.grey.shade300,
                    activeThumbColor: Colors.white,
                    inactiveThumbColor: Colors.white,
                    activeTextColor: Colors.deepPurple,
                    inactiveTextColor: Colors.black87,
                  ),
                ),
              ),
            ),
            _Section(
              title: '4 · Theme defaults',
              subtitle:
                  'No per-widget style — uses SmoothToggleTheme (height 34)',
              child: _DemoRow(
                label: 'Themed',
                child: SmoothIOSToggle(
                  value: true,
                  onChanged: (_) {},
                ),
              ),
            ),
            _Section(
              title: '5 · Large toggle (max height)',
              subtitle:
                  'trackHeight 70, custom thumbWidth 90, drag & fitted text',
              child: _DemoRow(
                label: _premium ? 'Premium ON' : 'Premium OFF',
                child: SmoothIOSToggle(
                  value: _premium,
                  activeText: 'Premium',
                  inactiveText: 'Standard',
                  onChanged: (v) => setState(() => _premium = v),
                  style: SmoothToggleStyle(
                    trackWidth: 200,
                    trackHeight: SmoothToggleStyle.maxTrackHeight,
                    thumbWidth: 90,
                    trackPadding: 6,
                    activeTrackColor: Colors.amber.shade700,
                    inactiveTrackColor: Colors.grey.shade600,
                    activeTextColor: Colors.white,
                    inactiveTextColor: Colors.white,
                  ),
                ),
              ),
            ),
            _Section(
              title: '6 · Compact (min height)',
              subtitle: 'trackHeight 25 — smallest validated size',
              child: _DemoRow(
                label: _compact ? 'ON' : 'OFF',
                child: SmoothIOSToggle(
                  value: _compact,
                  activeText: 'ON',
                  inactiveText: 'OFF',
                  onChanged: (v) => setState(() => _compact = v),
                  style: const SmoothToggleStyle(
                    trackHeight: SmoothToggleStyle.minTrackHeight,
                    trackWidth: 88,
                  ),
                ),
              ),
            ),
            _Section(
              title: '7 · Disabled',
              subtitle: 'enabled: false — tap & drag ignored',
              child: _DemoRow(
                label: 'Locked',
                child: SmoothIOSToggle(
                  value: true,
                  enabled: false,
                  activeText: 'ON',
                  inactiveText: 'OFF',
                  onChanged: (_) {},
                ),
              ),
            ),
            _Section(
              title: '8 · No labels',
              subtitle: 'showText: false',
              child: _DemoRow(
                label: 'Minimal',
                child: SmoothIOSToggle(
                  value: true,
                  showText: false,
                  onChanged: (_) {},
                  style: const SmoothToggleStyle(trackWidth: 72),
                ),
              ),
            ),
            _Section(
              title: '9 · Default thumb clamp',
              subtitle: 'Narrow trackWidth 80 — thumb auto-clamped to fit',
              child: _DemoRow(
                label: 'Clamped',
                child: SmoothIOSToggle(
                  value: true,
                  onChanged: (_) {},
                  style: const SmoothToggleStyle(
                    trackWidth: 80,
                    trackHeight: 30,
                  ),
                ),
              ),
            ),
            _Section(
              title: '10 · Keyboard',
              subtitle: 'autofocus — press Space or Enter to toggle',
              child: _KeyboardDemo(),
            ),
          ],
        ),
      ),
    );
  }
}

class _IntroBanner extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Card(
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(
              '0.2.0 feature tour',
              style: Theme.of(context).textTheme.titleMedium,
            ),
            const SizedBox(height: 8),
            Text(
              'Try tap, horizontal drag, and (section 10) keyboard toggling. '
              'Labels scale to fit; thumb width is validated and clamped.',
              style: Theme.of(context).textTheme.bodySmall,
            ),
          ],
        ),
      ),
    );
  }
}

class _Section extends StatelessWidget {
  const _Section({
    required this.title,
    required this.subtitle,
    required this.child,
  });

  final String title;
  final String subtitle;
  final Widget child;

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.only(bottom: 20),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text(title, style: Theme.of(context).textTheme.titleSmall),
          const SizedBox(height: 2),
          Text(subtitle, style: Theme.of(context).textTheme.bodySmall),
          const SizedBox(height: 12),
          child,
        ],
      ),
    );
  }
}

class _DemoRow extends StatelessWidget {
  const _DemoRow({required this.label, required this.child});

  final String label;
  final Widget child;

  @override
  Widget build(BuildContext context) {
    return Row(
      children: [
        Expanded(
          child: _StatusChip(text: label),
        ),
        const SizedBox(width: 12),
        child,
      ],
    );
  }
}

class _StatusChip extends StatelessWidget {
  const _StatusChip({required this.text});

  final String text;

  @override
  Widget build(BuildContext context) {
    return Container(
      padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
      decoration: BoxDecoration(
        color: Theme.of(context).colorScheme.surfaceContainerHighest,
        borderRadius: BorderRadius.circular(8),
      ),
      child: Text(
        text,
        style: Theme.of(context).textTheme.labelMedium,
      ),
    );
  }
}

/// Isolated demo so autofocus does not steal focus from other rows.
class _KeyboardDemo extends StatefulWidget {
  @override
  State<_KeyboardDemo> createState() => _KeyboardDemoState();
}

class _KeyboardDemoState extends State<_KeyboardDemo> {
  bool _value = false;

  @override
  Widget build(BuildContext context) {
    return _DemoRow(
      label: _value ? 'Focused ON' : 'Focused OFF',
      child: SmoothIOSToggle(
        value: _value,
        autofocus: true,
        activeText: 'ON',
        inactiveText: 'OFF',
        onChanged: (v) => setState(() => _value = v),
      ),
    );
  }
}
1
likes
150
points
4
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

A smooth, fully customizable iOS-style toggle with a pill thumb (~1.7× height), adaptive labels beside or inside the thumb, drag gestures, validated sizing, and built-in Flutter state management (ValueNotifier / InheritedWidget).

Repository (GitHub)
View/report issues
Contributing

Topics

#toggle #switch #widget #animation #ios

License

MIT (license)

Dependencies

flutter

More

Packages that depend on smooth_ios_style_toggle