Skip to content

Props & Constructors

In React you pass data to a component via props — a plain object destructured in the function signature. In Flutter you pass data to a widget via constructor parameters declared as final fields on the class. The two models solve the exact same problem: making a widget configurable from the outside without coupling it to a specific data source.

Flutter uses Dart’s named parameters extensively. Named parameters are opt-in: wrap them in {} and they become keyword arguments at the call site. Mark one required and Dart enforces it at compile time — no more PropTypes or TypeScript’s Required<>.

React
// React — TypeScript props
interface BadgeProps {
label: string;
color?: string; // optional, has default
count: number; // required
}
function Badge({ label, color = 'blue', count }: BadgeProps) {
return (
<span style={{ background: color }}>
{label} ({count})
</span>
);
}
// Usage
<Badge label="Notifications" count={5} />
<Badge label="Alerts" color="red" count={2} />
Flutter
// Flutter — constructor params
class Badge extends StatelessWidget {
final String label;
final Color color; // optional with default
final int count; // required
const Badge({
super.key,
required this.label,
this.color = Colors.blue, // default value
required this.count,
});
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
decoration: BoxDecoration(
color: color,
borderRadius: BorderRadius.circular(12),
),
child: Text(
'$label ($count)',
style: const TextStyle(color: Colors.white),
),
);
}
}
// Usage
Badge(label: 'Notifications', count: 5)
Badge(label: 'Alerts', color: Colors.red, count: 2)
import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        backgroundColor: Colors.grey[100],
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: const [
              StatusBadge(label: 'Active', status: BadgeStatus.success),
              SizedBox(height: 12),
              StatusBadge(label: 'Pending', status: BadgeStatus.warning),
              SizedBox(height: 12),
              StatusBadge(label: 'Failed', status: BadgeStatus.error),
            ],
          ),
        ),
      ),
    );
  }
}

enum BadgeStatus { success, warning, error }

class StatusBadge extends StatelessWidget {
  final String label;
  final BadgeStatus status;
  final double fontSize;

  const StatusBadge({
    super.key,
    required this.label,
    required this.status,
    this.fontSize = 14,
  });

  Color get _color {
    switch (status) {
      case BadgeStatus.success: return Colors.green;
      case BadgeStatus.warning: return Colors.orange;
      case BadgeStatus.error:   return Colors.red;
    }
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
      decoration: BoxDecoration(
        color: _color.withOpacity(0.15),
        border: Border.all(color: _color),
        borderRadius: BorderRadius.circular(20),
      ),
      child: Text(
        label,
        style: TextStyle(color: _color, fontSize: fontSize,
            fontWeight: FontWeight.w600),
      ),
    );
  }
}
How do you declare a required named parameter in a Flutter widget constructor?
Why are widget fields declared `final` in Flutter?
What does the `this.fieldName` shorthand in a Dart constructor parameter list do?