Atomic Design micro-UI architecture
90
90%
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Passed
No known issues
Enforces a strict Atomic Design classification for micro-UI components in Flutter DDD projects with hybrid state management (hooks_riverpod + flutter_bloc).
Use this tree to classify every UI component before writing code:
Is this component ONLY displaying data passed via constructor?
├── YES → Atom (StatelessWidget)
│ No controllers, no ref, no mutable state.
│ Examples: AppLabel, StatusBadge, PriceTag, IconTile
│
└── NO → Does it need local controllers OR Riverpod access?
├── YES → Molecule (HookConsumerWidget)
│ Uses useTextEditingController, useFocusNode,
│ ref.watch, ref.read, useState, etc.
│ Examples: SearchBar, EmailField, QuantitySelector
│
└── NO → Re-evaluate. If truly no state, use Atom.Atoms are the smallest UI building blocks. They:
final constructor parametersconst constructors when possibleclass PriceTag extends StatelessWidget {
const PriceTag({super.key, required this.amount, required this.currency});
final double amount;
final String currency;
@override
Widget build(BuildContext context) {
return Text('$currency ${amount.toStringAsFixed(2)}');
}
}Molecules combine atoms with local state and/or Riverpod access. They:
HookConsumerWidget from hooks_riverpoduseTextEditingController, useFocusNode, useState)ref.watch / ref.readValidator class for input validationclass EmailField extends HookConsumerWidget {
const EmailField({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final controller = useTextEditingController();
final focusNode = useFocusNode();
return TextFormField(
controller: controller,
focusNode: focusNode,
validator: EmailFieldValidator.validate,
decoration: const InputDecoration(labelText: 'Email'),
onFieldSubmitted: (value) {
ref.read(authFormProvider.notifier).updateEmail(value);
},
);
}
}Every molecule with user input MUST have a companion Validator:
/// Validator for EmailField molecule.
class EmailFieldValidator {
EmailFieldValidator._();
static String? validate(String? value) {
if (value == null || value.isEmpty) return 'Email is required';
if (!RegExp(r'^[\w-.]+@([\w-]+\.)+[\w-]{2,4}$').hasMatch(value)) {
return 'Enter a valid email address';
}
return null;
}
}Validators are pure functions with no dependencies. They:
{molecule_name}_validator.dart alongside the moleculestatic String? validate(String? value) per fieldTextFormField.validator directlyEvery HookConsumerWidget molecule MUST have a widget test:
void main() {
group('EmailField', () {
testWidgets('renders with empty initial state', (tester) async {
await tester.pumpWidget(
ProviderScope(
overrides: [
authFormProvider.overrideWith(() => MockAuthFormNotifier()),
],
child: const MaterialApp(home: Scaffold(body: Form(child: EmailField()))),
),
);
expect(find.byType(TextFormField), findsOneWidget);
});
testWidgets('shows error for invalid email', (tester) async {
await tester.pumpWidget(/* ... ProviderScope wrapper ... */);
final formKey = GlobalKey<FormState>();
// Enter invalid email, trigger validation, assert error text
});
testWidgets('calls provider on valid submission', (tester) async {
// Enter valid email, submit, verify ref.read was called
});
});
}lib/
├── shared/
│ └── widgets/
│ ├── atoms/ ← StatelessWidget components
│ │ ├── price_tag.dart
│ │ └── status_badge.dart
│ └── molecules/ ← HookConsumerWidget components
│ ├── email_field.dart
│ ├── email_field_validator.dart
│ ├── search_bar.dart
│ └── search_bar_validator.dart
├── features/{feature}/
│ └── presentation/
│ └── widgets/
│ ├── atoms/
│ └── molecules/
test/
├── shared/
│ └── widgets/
│ └── molecules/
│ └── email_field_test.dart
└── features/{feature}/
└── presentation/
└── widgets/
└── molecules/hooks_riverpod, flutter_hooks, mocktail (dev)ConsumerWidget without hooks, or StatefulWidget for local state