Flutter Custom Widgets and Themes

Writing the same card layout or button style in five screens wastes time and creates inconsistency. Custom widgets let you build reusable UI components. Themes give your entire app a unified look from a single configuration.

Why Build Custom Widgets

  Without custom widgets:
  ────────────────────────────────────────────────
  ProductScreen: Card → Padding → Column → Image + Text + Button
  OrderScreen:   Card → Padding → Column → Image + Text + Button
  WishlistScreen:Card → Padding → Column → Image + Text + Button
  (Repeated 3 times — change one, must change all three)

  With a custom widget:
  ────────────────────────────────────────────────
  ProductScreen:   ProductCard(product: p)
  OrderScreen:     ProductCard(product: p)
  WishlistScreen:  ProductCard(product: p)
  (Change once → all three update automatically)

Creating a Reusable Custom Widget

class ProductCard extends StatelessWidget {
  final String name;
  final String imageUrl;
  final double price;
  final VoidCallback onAddToCart;

  const ProductCard({
    required this.name,
    required this.imageUrl,
    required this.price,
    required this.onAddToCart,
  });

  @override
  Widget build(BuildContext context) {
    return Card(
      elevation: 4,
      shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          ClipRRect(
            borderRadius: BorderRadius.vertical(top: Radius.circular(12)),
            child: Image.network(imageUrl, height: 140, width: double.infinity, fit: BoxFit.cover),
          ),
          Padding(
            padding: EdgeInsets.all(12),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text(name, style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
                SizedBox(height: 4),
                Text('₹${price.toStringAsFixed(0)}', style: TextStyle(color: Colors.green, fontSize: 15)),
                SizedBox(height: 10),
                SizedBox(
                  width: double.infinity,
                  child: ElevatedButton(onPressed: onAddToCart, child: Text('Add to Cart')),
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }
}

Using the Custom Widget

ProductCard(
  name: 'Running Shoes',
  imageUrl: 'https://example.com/shoes.jpg',
  price: 2499,
  onAddToCart: () => print('Added!'),
)

Custom Widget with Optional Parameters

class InfoBadge extends StatelessWidget {
  final String label;
  final Color color;
  final IconData? icon;       // Optional icon

  const InfoBadge({
    required this.label,
    this.color = Colors.blue, // Default value
    this.icon,
  });

  @override
  Widget build(BuildContext context) {
    return Container(
      padding: EdgeInsets.symmetric(horizontal: 10, vertical: 4),
      decoration: BoxDecoration(
        color: color.withOpacity(0.15),
        border: Border.all(color: color),
        borderRadius: BorderRadius.circular(20),
      ),
      child: Row(
        mainAxisSize: MainAxisSize.min,
        children: [
          if (icon != null) ...[
            Icon(icon, size: 14, color: color),
            SizedBox(width: 4),
          ],
          Text(label, style: TextStyle(color: color, fontSize: 12)),
        ],
      ),
    );
  }
}

// Usage
InfoBadge(label: 'In Stock', color: Colors.green, icon: Icons.check)
InfoBadge(label: 'New', color: Colors.orange)
InfoBadge(label: 'Sale', color: Colors.red, icon: Icons.local_offer)

ThemeData — App-Wide Styling

Define your app's colors, fonts, and component styles in one ThemeData object. Every widget reads from it automatically.

MaterialApp(
  title: 'My App',
  theme: ThemeData(
    // Color scheme
    colorScheme: ColorScheme.fromSeed(seedColor: Colors.indigo),
    useMaterial3: true,

    // Typography
    textTheme: TextTheme(
      headlineLarge: TextStyle(fontSize: 32, fontWeight: FontWeight.bold, color: Colors.indigo),
      bodyLarge: TextStyle(fontSize: 16, color: Colors.black87),
      labelSmall: TextStyle(fontSize: 11, color: Colors.grey),
    ),

    // Button styling for all ElevatedButtons
    elevatedButtonTheme: ElevatedButtonThemeData(
      style: ElevatedButton.styleFrom(
        backgroundColor: Colors.indigo,
        foregroundColor: Colors.white,
        padding: EdgeInsets.symmetric(horizontal: 24, vertical: 12),
        shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
      ),
    ),

    // Input field styling for all TextFields
    inputDecorationTheme: InputDecorationTheme(
      border: OutlineInputBorder(borderRadius: BorderRadius.circular(8)),
      filled: true,
      fillColor: Colors.grey.shade100,
    ),

    // Card styling
    cardTheme: CardTheme(
      elevation: 3,
      shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
    ),
  ),
  home: HomeScreen(),
)

Accessing Theme in Widgets

Widget build(BuildContext context) {
  final theme = Theme.of(context);
  final colors = theme.colorScheme;

  return Text(
    'Primary Color Text',
    style: theme.textTheme.headlineLarge,  // Uses theme font
  );
}

Dark Theme Support

MaterialApp(
  theme: ThemeData.light(useMaterial3: true).copyWith(
    colorScheme: ColorScheme.fromSeed(seedColor: Colors.indigo),
  ),
  darkTheme: ThemeData.dark(useMaterial3: true).copyWith(
    colorScheme: ColorScheme.fromSeed(
      seedColor: Colors.indigo,
      brightness: Brightness.dark,
    ),
  ),
  themeMode: ThemeMode.system,  // Follows device setting
  home: HomeScreen(),
)
  ThemeMode options:
  ───────────────────────────────────
  ThemeMode.system  → follows device (auto)
  ThemeMode.light   → always light
  ThemeMode.dark    → always dark

Custom Fonts

  1. Download font files and place in assets/fonts/
  2. Register in pubspec.yaml:
     flutter:
       fonts:
         - family: Poppins
           fonts:
             - asset: assets/fonts/Poppins-Regular.ttf
             - asset: assets/fonts/Poppins-Bold.ttf
               weight: 700

  3. Use in theme:
     textTheme: TextTheme(
       bodyLarge: TextStyle(fontFamily: 'Poppins'),
     )

Summary

  • Extract repeated UI patterns into custom StatelessWidgets with required and optional parameters.
  • Define ThemeData once in MaterialApp — all widgets inherit the styles automatically.
  • Use Theme.of(context) inside widgets to read current theme values.
  • Support dark mode with darkTheme and ThemeMode.system.

Leave a Comment

Your email address will not be published. Required fields are marked *