formwise

Smart form fields for Flutter with built-in validation, auto-formatting, typo detection, and animated error feedback.

CI pub package License: MIT

Features

  • Smart Validators — Email (Levenshtein-based typo detection across 55+ domains), phone, URL, credit card (Luhn), postal code (20 countries), numeric range, pattern, and composable validators.
  • Auto-Formatters — Credit card, phone (8 country presets), smart date (digit clamping), currency, postal code, custom mask, title case, uppercase/lowercase, trimmed.
  • SmartTextFormField — Drop-in TextFormField replacement with 7 named constructors (.email(), .phone(), .creditCard(), .url(), .password(), .numeric(), .postalCode()), debounced validation, async validation, shake-on-error, and animated success/error indicators.
  • Animated Validation UXAnimatedValidationIcon (crossfade between states), StrengthIndicator (animated bar), AnimatedValidationBorder, PasswordStrength calculator.
  • SmartFormController — Manage multiple fields: validate all, reset, get/set values, track dirty state.
  • Postal Code Validation — Format validation and auto-formatting for 20 countries (US, UK, Canada, Japan, Germany, France, Australia, India, Brazil, Nigeria, and more).
  • ML Module (optional) — On-device TFLite email typo detection with automatic edit-distance fallback. Import formwise_ml.dart separately.

Installation

dependencies:
  formwise: ^1.1.0
flutter pub add formwise

Quick Start

Named Constructors

The fastest way to get a fully configured field:

import 'package:formwise/formwise.dart';

// Email with typo detection, lowercase formatting, email keyboard
SmartTextFormField.email(
  name: 'email',
  onTypoDetected: (suggestion) => print('Did you mean $suggestion?'),
)

// Phone with mask formatting and digit keyboard
SmartTextFormField.phone(
  name: 'phone',
  mask: '(###) ###-####',
)

// Credit card with Luhn validation and auto-formatting
SmartTextFormField.creditCard(name: 'card')

// Password with strength requirements
SmartTextFormField.password(
  name: 'password',
  minLength: 8,
  requireUppercase: true,
  requireDigit: true,
  requireSpecialChar: true,
)

// URL with optional HTTPS enforcement
SmartTextFormField.url(name: 'website', requireHttps: true)

// Numeric with range validation
SmartTextFormField.numeric(name: 'age', min: 0, max: 150)

// Postal code with country-specific format
SmartTextFormField.postalCode(
  name: 'zip',
  country: PostalCountry.us,
)

Each named constructor pre-configures the validator, formatter, keyboard type, icon, label, and hint text.

Validators

Email with Typo Detection

Uses Levenshtein edit distance against 55+ known email providers. Catches typos like gmial.comgmail.com, yhaoo.comyahoo.com.

final validate = SmartValidators.email(
  suggestCorrection: (suggestion) {
    // suggestion = "user@gmail.com" when user types "user@gmial.com"
  },
);

// Add your organization's domains to the known-good list:
final validate = SmartValidators.email(
  customDomains: ['mycompany.com', 'mycompany.org'],
);

// Adjust sensitivity (default maxTypoDistance: 2):
final strict = SmartValidators.email(maxTypoDistance: 1);

Using EmailTypoDetector Directly

const detector = EmailTypoDetector();

// Check a domain
final match = detector.detectTypo('gmial.com');
// match?.suggested == 'gmail.com', match?.distance == 2

// Check a full email
final corrected = detector.suggestCorrection('user@gmial.com');
// corrected == 'user@gmail.com'

// Custom domain list
final custom = EmailTypoDetector(
  domains: [...EmailTypoDetector.defaultDomains, 'mycompany.com'],
);

Phone

SmartValidators.phone(
  minDigits: 10,
  maxDigits: 10,
  allowCountryCode: false, // reject numbers starting with +
)

URL

SmartValidators.url(
  requireHttps: true,
  allowedSchemes: ['https', 'ftp'],
)

Credit Card (Luhn)

SmartValidators.creditCard()

// Detect brand:
final brand = SmartValidators.detectCardBrand('4111111111111111');
// CardBrand.visa, .mastercard, .amex, .discover, .unknown

Postal Code (20 Countries)

// Validate for a specific country
final validate = PostalCodeValidator.forCountry(PostalCountry.uk);
validate('SW1A 1AA'); // null (valid)
validate('12345');     // 'Invalid postal code for UK (e.g. SW1A 1AA)'

// Auto-detect country format
final validate = PostalCodeValidator.autoDetect();

// Detect which countries match a code
final countries = PostalCodeValidator.detectCountry('12345');
// [PostalCountry.us, PostalCountry.germany, PostalCountry.france, ...]

Supported countries: US, UK, Canada, Japan, Germany, France, Australia, India, Brazil, Nigeria, Netherlands, Italy, Spain, South Korea, China, Russia, Mexico, Switzerland, Poland, Sweden.

Composing Validators

SmartValidators.compose([
  SmartValidators.required(errorMessage: 'Required'),
  SmartValidators.minLength(length: 8),
  SmartValidators.pattern(
    regex: RegExp(r'[A-Z]'),
    errorMessage: 'Must contain an uppercase letter',
  ),
])

Other Validators

SmartValidators.required()
SmartValidators.minLength(length: 3)
SmartValidators.maxLength(length: 100)
SmartValidators.numericRange(min: 0, max: 999, allowDecimals: false)
SmartValidators.pattern(regex: RegExp(r'^\d+$'), errorMessage: 'Digits only')

Formatters

Phone

// Custom mask
SmartFormatters.phone(mask: '(###) ###-####')

// Country presets
SmartFormatters.phoneInternational(PhoneFormat.japan)   // ###-####-####
SmartFormatters.phoneInternational(PhoneFormat.nigeria)  // #### ### ####
SmartFormatters.phoneInternational(PhoneFormat.uk)       // +44 #### ######

Presets: PhoneFormat.us, .uk, .japan, .nigeria, .germany, .india, .brazil, .international

Smart Date (with Digit Clamping)

Auto-clamps month to 01–12 and day to 01–31 (adjusted per month) as the user types:

SmartFormatters.smartDate(DateFormat.mmddyyyy)    // MM/DD/YYYY
SmartFormatters.smartDate(DateFormat.ddmmyyyy)    // DD/MM/YYYY
SmartFormatters.smartDate(DateFormat.iso)          // YYYY-MM-DD
SmartFormatters.smartDate(DateFormat.ddmmyyyyDot)  // DD.MM.YYYY
SmartFormatters.smartDate(DateFormat.yyyymmdd)     // YYYY/MM/DD

Credit Card

SmartFormatters.creditCard()                      // 4-4-4-4 standard
SmartFormatters.creditCard(amexFormat: true)       // 4-6-5 Amex
SmartFormatters.creditCard(autoDetectBrand: true)  // switches by prefix

Currency

SmartFormatters.currency()                         // $1,234.56
SmartFormatters.currency(symbol: '€', showSymbol: true)
SmartFormatters.currency(symbol: '', separator: '.', decimal: ',') // 1.234,56

Postal Code

PostalCodeFormatter(PostalCountry.us)     // 12345 or 12345-6789
PostalCodeFormatter(PostalCountry.japan)  // 123-4567
PostalCodeFormatter(PostalCountry.uk)     // SW1A 1AA
PostalCodeFormatter(PostalCountry.canada) // K1A 0B1

Other Formatters

SmartFormatters.mask('###-##-####')    // SSN: 123-45-6789
SmartFormatters.titleCase()            // "john doe" → "John Doe"
SmartFormatters.uppercase()
SmartFormatters.lowercase()
SmartFormatters.trimmed()              // collapses multiple spaces
SmartFormatters.digitsOnly()

Animated Validation UX

AnimatedValidationIcon

Crossfades between idle/spinner/checkmark/error with scale+fade transitions. Used internally by SmartTextFormField, or use standalone:

AnimatedValidationIcon(
  state: ValidationState.valid,  // .idle, .validating, .valid, .invalid
  validColor: Colors.green,
  invalidColor: Colors.red,
  size: 20,
)

StrengthIndicator

Animated bar from 0.0 to 1.0 with red→amber→green color interpolation:

StrengthIndicator(
  strength: PasswordStrength.calculate(password),
  label: PasswordStrength.label(strength), // "Very weak" → "Very strong"
  height: 4,
)

PasswordStrength

final strength = PasswordStrength.calculate('MyP@ss123');  // 0.0–1.0
final label = PasswordStrength.label(strength);            // "Strong"

Scores based on: length, character variety (lowercase, uppercase, digits, special), uniqueness ratio.

AnimatedValidationBorder

Wraps any widget with animated border color per validation state:

AnimatedValidationBorder(
  state: ValidationState.valid,
  child: TextField(...),
)

SmartFormController

Manage multiple fields programmatically:

final controller = SmartFormController();

// Register fields via formController parameter:
SmartTextFormField.email(name: 'email', formController: controller)
SmartTextFormField.password(name: 'password', formController: controller)

// Validate all fields:
if (controller.validate()) {
  final data = controller.values; // {'email': '...', 'password': '...'}
}

// Other operations:
controller.reset();                    // restore initial values, clear errors
controller.isDirty;                    // any field modified?
controller.isValid;                    // all fields valid?
controller.errors;                     // {'email': 'error msg'} — only failing
controller.getValue('email');          // get one field
controller.setValue('email', 'new');   // set one field

Async Validation

For server-side checks (username availability, etc.):

SmartTextFormField(
  name: 'username',
  asyncValidator: (value) async {
    final available = await api.checkUsername(value!);
    return available ? null : 'Username is already taken';
  },
  validationDebounce: Duration(milliseconds: 500),
)

Shows an animated spinner during validation, automatically cancels stale requests when the user keeps typing.

ML Module (Optional)

For on-device ML email validation using TFLite. It's split into a separate formwise_ml.dart entry point so the ML API surface doesn't clutter the core import — but note that tflite_flutter is a package dependency either way, so its native binaries are bundled into your app regardless of whether you import formwise_ml.dart.

import 'package:formwise/formwise_ml.dart';

final validator = EmailMlValidator(
  modelAsset: 'assets/models/email_typo.tflite',
  labels: ['valid', 'gmail.com', 'yahoo.com', 'outlook.com'],
);
await validator.initialize();

final result = await validator.validateEmail('user@gmial.com');
// result.isValid == false
// result.suggestion == 'user@gmail.com'
// result.confidence == 0.92

Falls back to edit-distance detection when the model isn't available.

API Reference

SmartValidators

Validator Description
email() Email with Levenshtein typo detection (55+ domains)
phone() Phone number with configurable digit range
url() URL with optional HTTPS enforcement
creditCard() Credit card via Luhn algorithm
numericRange() Number within min/max bounds
pattern() Custom regex pattern
required() Non-empty check
minLength() Minimum character count
maxLength() Maximum character count
compose() Combine multiple validators (first error wins)
detectCardBrand() Visa, Mastercard, Amex, Discover detection

SmartFormatters

Formatter Example Output
creditCard() 4111 1111 1111 1111
phone() (234) 567-8901
smartDate() 12/31/2025 (with digit clamping)
currency() $1,234.56
mask() ###-##-####123-45-6789
titleCase() John Doe
uppercase() / lowercase() Case conversion
trimmed() Collapse multiple spaces
digitsOnly() Strip non-digits

PostalCodeValidator

Method Description
forCountry() Validate against a specific country format
autoDetect() Try all country formats
detectCountry() List countries matching a code

Animated Widgets

Widget Description
AnimatedValidationIcon Crossfade spinner/check/error icons
StrengthIndicator Animated strength bar with color gradient
AnimatedValidationBorder Animated border per validation state
PasswordStrength Calculate and label password strength

SmartTextFormField Named Constructors

Constructor Pre-configured
.email() Email validator + typo detection, lowercase, email keyboard
.phone() Phone validator, mask formatter, phone keyboard
.creditCard() Luhn validator, card formatter, number keyboard
.url() URL validator, lowercase, URL keyboard
.password() Composed strength validator, obscured text
.numeric() Range validator, number keyboard
.postalCode() Country validator + formatter, characters capitalization

Example

See the example app for a complete demo with 5 interactive screens.

License

MIT License — see LICENSE for details.

Libraries

formwise
ML-powered smart form fields for Flutter with auto-validation, auto-formatting, and typo detection.
formwise_ml
Optional ML-powered validation module for formwise.