form_flutter 0.0.2 copy "form_flutter: ^0.0.2" to clipboard
form_flutter: ^0.0.2 copied to clipboard

A Flutter package for reusable forms, validators, and field catalogs.

example/lib/main.dart

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

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'form_flutter example',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(
          seedColor: const Color(0xFF155EEF),
          brightness: Brightness.light,
        ),
        scaffoldBackgroundColor: const Color(0xFFF4F7FB),
        inputDecorationTheme: InputDecorationTheme(
          filled: true,
          fillColor: Colors.white,
          border: OutlineInputBorder(
            borderRadius: BorderRadius.circular(20),
            borderSide: const BorderSide(color: Color(0xFFD0D5DD)),
          ),
          enabledBorder: OutlineInputBorder(
            borderRadius: BorderRadius.circular(20),
            borderSide: const BorderSide(color: Color(0xFFD0D5DD)),
          ),
          focusedBorder: OutlineInputBorder(
            borderRadius: BorderRadius.circular(20),
            borderSide: const BorderSide(color: Color(0xFF155EEF), width: 1.5),
          ),
        ),
        useMaterial3: true,
      ),
      home: const ExampleHomePage(),
    );
  }
}

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

  @override
  State<ExampleHomePage> createState() => _ExampleHomePageState();
}

class _ExampleHomePageState extends State<ExampleHomePage> {
  final FormFlutterController _controller = FormFlutterController(
    initialValues: const {
      'fullName': '',
      'email': '',
      'phone': '',
      'phoneCountry': 'US',
      'password': '',
      'team': 'product',
      'interests': <String>['analytics'],
      'priority': 65.0,
      'available': true,
      'startDate': null,
    },
  );

  late final List<FormFlutterField<dynamic>> _fields = [
    FormFlutterTextField(
      name: 'fullName',
      label: 'Full name',
      decorationOverride: const InputDecoration(
        prefixIcon: Icon(Icons.person_outline),
        fillColor: Color(0xFFF8FBFF),
      ),
      validator: FormFlutterValidators.combine([
        FormFlutterValidators.requiredText(),
        FormFlutterValidators.minLength(3),
      ]),
    ),
    FormFlutterTextField(
      name: 'email',
      label: 'Email',
      keyboardType: TextInputType.emailAddress,
      decorationOverride: const InputDecoration(
        prefixIcon: Icon(Icons.mail_outline),
      ),
      validator: FormFlutterValidators.combine([
        FormFlutterValidators.requiredText(),
        FormFlutterValidators.email(),
      ]),
      asyncValidator: FormFlutterValidators.uniqueEmail(
        (email, _) async => email != 'taken@example.com',
      ),
    ),
    FormFlutterPhoneField(
      name: 'phone',
      label: 'Phone',
      countryFieldName: 'phoneCountry',
      initialCountryCode: 'US',
      showCountryFlagInSelector: false,
      showCountryIsoCodeInSelector: true,
      showCountryDialCodeInSelector: true,
      nationalNumberHintText: 'Enter phone number',
      validator: FormFlutterValidators.combine([
        FormFlutterValidators.requiredText(),
        FormFlutterValidators.numericText(),
      ]),
    ),
    FormFlutterTextField(
      name: 'password',
      label: 'Password',
      hintText: 'Create password',
      obscureText: true,
      enableObscureTextToggle: true,
      decorationOverride: const InputDecoration(
        prefixIcon: Icon(Icons.lock_outline),
      ),
      validator: FormFlutterPresetValidators.strongPassword(),
    ),
    FormFlutterDropdownField<String>(
      name: 'team',
      label: 'Team',
      decorationOverride: const InputDecoration(
        prefixIcon: Icon(Icons.groups_2_outlined),
      ),
      options: const [
        FormFlutterOption(
          value: 'design',
          label: 'Design',
          color: Color(0xFF7C3AED),
          icon: Icons.brush_outlined,
          indicatorSize: 16,
        ),
        FormFlutterOption(
          value: 'product',
          label: 'Product',
          color: Color(0xFFEA580C),
          icon: Icons.insights_outlined,
          indicatorSize: 16,
        ),
        FormFlutterOption(
          value: 'engineering',
          label: 'Engineering',
          color: Color(0xFF2563EB),
          icon: Icons.code,
          indicatorSize: 16,
        ),
      ],
      validator: FormFlutterValidators.requiredSelection<String>(),
    ),
    FormFlutterMultiSelectField<String>(
      name: 'interests',
      label: 'Interests',
      options: [
        const FormFlutterOption(
          value: 'automation',
          label: 'Automation',
          color: Color(0xFFEA580C),
          backgroundColor: Color(0xFFFFF7ED),
          selectedColor: Color(0xFFEA580C),
          selectedTextColor: Colors.white,
          icon: Icons.bolt_outlined,
        ),
        FormFlutterOption(
          value: 'analytics',
          label: 'Analytics',
          color: const Color(0xFFDC2626),
          backgroundColor: const Color(0xFFFEF2F2),
          selectedColor: const Color(0xFFDC2626),
          selectedTextColor: Colors.white,
          icon: Icons.monitor_outlined,
        ),
        const FormFlutterOption(
          value: 'accessibility',
          label: 'Accessibility',
          color: Color(0xFF7C3AED),
          backgroundColor: Color(0xFFF5F3FF),
          selectedColor: Color(0xFF7C3AED),
          selectedTextColor: Colors.white,
          icon: Icons.accessibility_new,
        ),
      ],
      optionBuilder: (context, option, isSelected) {
        return AnimatedContainer(
          duration: const Duration(milliseconds: 180),
          padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12),
          decoration: BoxDecoration(
            color: isSelected
                ? (option.selectedColor ?? option.color ?? Colors.blue)
                : (option.backgroundColor ?? Colors.white),
            borderRadius: BorderRadius.circular(18),
            border: Border.all(
              color:
                  option.borderColor ?? option.color ?? const Color(0xFFD0D5DD),
            ),
          ),
          child: Row(
            mainAxisSize: MainAxisSize.min,
            children: [
              if (option.icon != null) ...[
                Icon(
                  option.icon,
                  size: 16,
                  color: isSelected
                      ? (option.selectedTextColor ?? Colors.white)
                      : (option.color ?? const Color(0xFF344054)),
                ),
                const SizedBox(width: 8),
              ],
              Text(
                option.label,
                style: TextStyle(
                  fontWeight: FontWeight.w700,
                  color: isSelected
                      ? (option.selectedTextColor ?? Colors.white)
                      : (option.textColor ?? const Color(0xFF344054)),
                ),
              ),
            ],
          ),
        );
      },
      validator: FormFlutterValidators.minItems<String>(1),
    ),
    FormFlutterSliderField(
      name: 'priority',
      label: 'Priority',
      min: 0,
      max: 100,
      divisions: 20,
      unitLabel: 'pts',
      activeColor: const Color(0xFF155EEF),
      inactiveColor: const Color(0xFFD0D5DD),
      thumbColor: const Color(0xFF155EEF),
      valueStyle: const TextStyle(
        fontWeight: FontWeight.w700,
        color: Color(0xFF155EEF),
      ),
    ),
    FormFlutterDateField(
      name: 'startDate',
      label: 'Start date',
      decorationOverride: const InputDecoration(
        prefixIcon: Icon(Icons.calendar_month_outlined),
      ),
      validator: FormFlutterValidators.requiredDate(),
    ),
    FormFlutterSwitchField(
      name: 'available',
      label: 'Available for contact',
      activeColor: const Color(0xFF155EEF),
      titleStyle: const TextStyle(fontWeight: FontWeight.w700),
      decoration: const InputDecoration(fillColor: Color(0xFFF8FBFF)),
    ),
  ];

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: DecoratedBox(
        decoration: const BoxDecoration(
          gradient: LinearGradient(
            begin: Alignment.topLeft,
            end: Alignment.bottomRight,
            colors: [Color(0xFFEFF6FF), Color(0xFFF4F7FB), Color(0xFFFFF7ED)],
          ),
        ),
        child: SafeArea(
          child: Center(
            child: ConstrainedBox(
              constraints: const BoxConstraints(maxWidth: 980),
              child: Padding(
                padding: const EdgeInsets.all(24),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    const _ExampleHeader(),
                    const SizedBox(height: 20),
                    Expanded(
                      child: LayoutBuilder(
                        builder: (context, constraints) {
                          final compact = constraints.maxWidth < 820;
                          final form = _ExampleCard(
                            child: DynamicFormFlutter(
                              controller: _controller,
                              fields: _fields,
                              submitLabel: 'Submit example form',
                              header: Column(
                                crossAxisAlignment: CrossAxisAlignment.start,
                                children: [
                                  Text(
                                    'Styled example',
                                    style: Theme.of(context)
                                        .textTheme
                                        .headlineSmall
                                        ?.copyWith(fontWeight: FontWeight.w800),
                                  ),
                                  const SizedBox(height: 8),
                                  Text(
                                    'This sample uses decoration overrides, option colors, icons, a country-aware phone field, and a custom chip builder.',
                                    style: Theme.of(context)
                                        .textTheme
                                        .bodyMedium
                                        ?.copyWith(
                                          color: const Color(0xFF475467),
                                        ),
                                  ),
                                  const SizedBox(height: 24),
                                  _ExampleGrid(
                                    children: [
                                      _ExampleFieldCard(
                                        child: _fields[0].buildField(_controller),
                                      ),
                                      _ExampleFieldCard(
                                        child: _fields[1].buildField(_controller),
                                      ),
                                      _ExampleFieldCard(
                                        child: _fields[2].buildField(_controller),
                                      ),
                                      _ExampleFieldCard(
                                        child: _fields[3].buildField(_controller),
                                      ),
                                    ],
                                  ),
                                  const SizedBox(height: 14),
                                  _ExampleFieldCard(
                                    child: _fields[4].buildField(_controller),
                                  ),
                                  const SizedBox(height: 14),
                                  _ExampleFieldCard(
                                    child: _fields[5].buildField(_controller),
                                  ),
                                  const SizedBox(height: 14),
                                  _ExampleGrid(
                                    children: [
                                      _ExampleFieldCard(
                                        child: _fields[6].buildField(_controller),
                                      ),
                                      _ExampleFieldCard(
                                        child: _fields[7].buildField(_controller),
                                      ),
                                      _ExampleFieldCard(
                                        child: _fields[8].buildField(_controller),
                                      ),
                                    ],
                                  ),
                                  const SizedBox(height: 24),
                                ],
                              ),
                              onSubmit: (values) {
                                ScaffoldMessenger.of(context).showSnackBar(
                                  SnackBar(
                                    content: Text('Submitted: ${values.asMap()}'),
                                  ),
                                );
                              },
                            ),
                          );

                          final preview = _ExampleCard(
                            dark: true,
                            child: ValueListenableBuilder<Map<String, Object?>>(
                              valueListenable: _controller.valuesListenable,
                              builder: (context, values, _) {
                                return Column(
                                  crossAxisAlignment: CrossAxisAlignment.start,
                                  children: [
                                    Text(
                                      'Live preview',
                                      style: Theme.of(context)
                                          .textTheme
                                          .titleLarge
                                          ?.copyWith(
                                            color: Colors.white,
                                            fontWeight: FontWeight.w800,
                                          ),
                                    ),
                                    const SizedBox(height: 16),
                                    Container(
                                      width: double.infinity,
                                      padding: const EdgeInsets.all(16),
                                      decoration: BoxDecoration(
                                        color: const Color(0xFF111827),
                                        borderRadius: BorderRadius.circular(18),
                                      ),
                                      child: Text(
                                        values.toString(),
                                        style: const TextStyle(
                                          color: Color(0xFFE5E7EB),
                                          fontFamily: 'monospace',
                                          height: 1.5,
                                        ),
                                      ),
                                    ),
                                  ],
                                );
                              },
                            ),
                          );

                          if (compact) {
                            return ListView(
                              children: [
                                form,
                                const SizedBox(height: 20),
                                preview,
                              ],
                            );
                          }

                          return Row(
                            crossAxisAlignment: CrossAxisAlignment.start,
                            children: [
                              Expanded(flex: 7, child: form),
                              const SizedBox(width: 20),
                              Expanded(flex: 4, child: preview),
                            ],
                          );
                        },
                      ),
                    ),
                  ],
                ),
              ),
            ),
          ),
        ),
      ),
    );
  }
}

class _ExampleHeader extends StatelessWidget {
  const _ExampleHeader();

  @override
  Widget build(BuildContext context) {
    final theme = Theme.of(context);
    return Container(
      width: double.infinity,
      padding: const EdgeInsets.all(28),
      decoration: BoxDecoration(
        borderRadius: BorderRadius.circular(28),
        gradient: const LinearGradient(
          colors: [Color(0xFF155EEF), Color(0xFF1D4ED8), Color(0xFF7C3AED)],
        ),
      ),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text(
            'form_flutter example',
            style: theme.textTheme.labelLarge?.copyWith(
              color: const Color(0xFFDBEAFE),
              fontWeight: FontWeight.w800,
              letterSpacing: 1.1,
            ),
          ),
          const SizedBox(height: 10),
          Text(
            'Detailed Styling Example',
            style: theme.textTheme.headlineMedium?.copyWith(
              color: Colors.white,
              fontWeight: FontWeight.w900,
            ),
          ),
        ],
      ),
    );
  }
}

class _ExampleCard extends StatelessWidget {
  const _ExampleCard({required this.child, this.dark = false});

  final Widget child;
  final bool dark;

  @override
  Widget build(BuildContext context) {
    return Container(
      padding: const EdgeInsets.all(24),
      decoration: BoxDecoration(
        color: dark ? const Color(0xFF0F172A) : Colors.white.withOpacity(0.94),
        borderRadius: BorderRadius.circular(28),
        border: Border.all(
          color: dark ? const Color(0xFF1F2937) : const Color(0xFFD7E2F2),
        ),
        boxShadow: const [
          BoxShadow(
            color: Color(0x14000000),
            blurRadius: 24,
            offset: Offset(0, 12),
          ),
        ],
      ),
      child: child,
    );
  }
}

class _ExampleGrid extends StatelessWidget {
  const _ExampleGrid({required this.children});

  final List<Widget> children;

  @override
  Widget build(BuildContext context) {
    return LayoutBuilder(
      builder: (context, constraints) {
        if (constraints.maxWidth < 620) {
          return Column(
            children: [
              for (var i = 0; i < children.length; i++) ...[
                children[i],
                if (i != children.length - 1) const SizedBox(height: 14),
              ],
            ],
          );
        }

        return Wrap(
          spacing: 14,
          runSpacing: 14,
          children: [
            for (final child in children)
              SizedBox(width: (constraints.maxWidth - 14) / 2, child: child),
          ],
        );
      },
    );
  }
}

class _ExampleFieldCard extends StatelessWidget {
  const _ExampleFieldCard({required this.child});

  final Widget child;

  @override
  Widget build(BuildContext context) {
    return Container(
      padding: const EdgeInsets.all(14),
      decoration: BoxDecoration(
        color: const Color(0xFFF8FAFC),
        borderRadius: BorderRadius.circular(22),
        border: Border.all(color: const Color(0xFFE4E7EC)),
      ),
      child: child,
    );
  }
}
0
likes
0
points
199
downloads

Publisher

unverified uploader

Weekly Downloads

A Flutter package for reusable forms, validators, and field catalogs.

Repository (GitHub)
View/report issues

License

unknown (license)

Dependencies

cupertino_icons, device_preview, flutter

More

Packages that depend on form_flutter