## Learning Objectives

By the end of this chapter, you will be able to:

- Apply app-wide theming with ThemeData and custom color schemes
- Create and reuse text styles, color palettes, and shape theming
- Build custom widgets (composition over inheritance) and make them configurable
- Use `InheritedWidget`/`Provider` concepts for style or configuration propagation
- Implement dark mode support and adaptive theming for different platforms

1

## Why Theming Matters

Consistent theming is fundamental to creating professional, maintainable Flutter applications.

### Benefits of Theming

- Coherent visual identity: Consistent theming gives your app a coherent visual identity and simplifies maintenance.
- Centralized styles: Centralized styles prevent duplicated styling code and make global changes straightforward.
- Accessibility support: Theming supports accessibility (contrasting colors, scalable text) and reduces visual bugs.

2

## App-Level Theming with ThemeData

Flutter's ThemeData provides a comprehensive system for app-wide styling.

### Setting Up Theming

Wrap your app with `MaterialApp(theme: ThemeData(...))` and provide `darkTheme` and `themeMode` for light/dark switching.

### Common ThemeData Fields

`primaryColor`, `colorScheme`, `textTheme`, `iconTheme`, `buttonTheme`, `inputDecorationTheme`, `appBarTheme`, `floatingActionButtonTheme`.

### Example Pattern (Conceptual)

    MaterialApp(
      title: 'Afrilen App',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        textTheme: TextTheme(
          headline6: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
          bodyText2: TextStyle(fontSize: 14),
        ),
        elevatedButtonTheme: ElevatedButtonThemeData(
          style: ElevatedButton.styleFrom(shape: StadiumBorder()),
        ),
      ),
      darkTheme: ThemeData.dark(),
      themeMode: ThemeMode.system,
      home: HomePage(),
    )

Guideline: Use `ColorScheme` and `fromSeed` or `ColorScheme.light/dark` to keep colors harmonious.

3

## Text Styles and Typography

Consistent typography is key to professional app design.

### TextTheme Usage

- Define a `TextTheme` in ThemeData and reference it via `Theme.of(context).textTheme`.
- Prefer semantic styles (headline, subtitle, body) instead of hard-coded sizes at use sites.

### Usage Example

    Text('Welcome', style: Theme.of(context).textTheme.headline6);

Best practice: Create a typography spec (font families, sizes, weights) and export as a centralized file for consistency.

4

## Color Palettes and Constants

Organizing colors systematically makes theming manageable and consistent.

### Color Token Organization

- Keep color tokens in one place (e.g., `app_colors.dart`) with semantic names: `primary`, `accent`, `background`, `surface`, `error`, `textPrimary`, `textSecondary`.
- Use semantic names at the widget level, not raw hex values.

### Example Token File (Conceptual)

    class AppColors {
      static const primary = Color(0xFF0066CC);
      static const accent = Color(0xFFFFC107);
      static const background = Color(0xFFF6F7FB);
    }

5

## Button, Input, and Card Theming

Theming common components ensures consistency across your app.

### Component Theming

- Override `ElevatedButtonThemeData`, `InputDecorationTheme`, and `CardTheme` in ThemeData so components look consistent across the app.
- Avoid styling buttons at every call site; style centrally and pass minimal overrides when needed.

### Example: InputDecorationTheme

    InputDecorationTheme(
      border: OutlineInputBorder(borderRadius: BorderRadius.circular(8)),
      filled: true,
      fillColor: Colors.white,
    )

6

## Dark Mode and Adaptive Theming

Supporting dark mode is essential for modern applications.

### Dark Mode Setup

- Provide both `theme` and `darkTheme`. Let users choose with `themeMode: ThemeMode.system/User/Light/Dark`.
- Ensure contrast and iconography are legible in both modes; test color combinations and images.

### Checklist for Dark Mode Readiness

- Test on large textScaleFactor values.
- Use semantic surface colors rather than fixed backgrounds.
- Prefer dynamic image variants or apply color filters to icons where appropriate.

7

## Creating Reusable Custom Widgets

Building custom widgets improves code reusability and maintainability.

### Best Practices

- Build custom widgets by composing existing widgets; prefer StatelessWidget for presentational widgets and StatefulWidget only when local state is necessary.
- Make widgets configurable via constructor parameters (text, colors, callbacks) and provide sensible defaults.

### Pattern: Small, Focused Widget

    class ProfileCard extends StatelessWidget {
      final String name;
      final String role;
      final VoidCallback? onFollow;

      const ProfileCard({
        Key? key,
        required this.name,
        required this.role,
        this.onFollow,
      }) : super(key: key);

      @override
      Widget build(BuildContext context) {
        return Card(
          child: Padding(
            padding: const EdgeInsets.all(12),
            child: Row(
              children: [
                CircleAvatar(child: Icon(Icons.person)),
                SizedBox(width: 12),
                Expanded(child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text(name, style: Theme.of(context).textTheme.subtitle1),
                    Text(role, style: Theme.of(context).textTheme.bodyText2),
                  ],
                )),
                ElevatedButton(onPressed: onFollow, child: Text('Follow')),
              ],
            ),
          ),
        );
      }
    }

Design note: Expose callbacks for interactivity and avoid tightly coupling widget to application services.

8

## Theming Custom Widgets

Custom widgets should integrate seamlessly with your app's theme.

### Theme Integration

Custom widgets should consume theme tokens (colors, text styles) rather than defining their own hard-coded styles. This keeps custom widgets consistent with the app theme.

### Example

    final titleStyle = Theme.of(context).textTheme.subtitle1?.copyWith(
      color: Theme.of(context).colorScheme.primary
    );

9

## Config and Style Propagation: InheritedWidget and Provider

Understanding how to propagate configuration and styles down the widget tree is essential for scalable apps.

### When to Use Each

- Use `InheritedWidget` for lightweight, performance-friendly propagation of config down the tree. For app-wide state or theme toggles, prefer `Provider` for ergonomics and testability.
- Example use cases: current locale, responsive breakpoints, feature flags, or custom theme overrides.

### Conceptual Pattern

    class AppConfig extends InheritedWidget {
      final String apiBaseUrl;
      const AppConfig({required this.apiBaseUrl, required Widget child}) : super(child: child);

      static AppConfig of(BuildContext context) => context.dependOnInheritedWidgetOfExactType()!;
      @override bool updateShouldNotify(covariant AppConfig old) => apiBaseUrl != old.apiBaseUrl;
    }

Guideline: Don't overuse; prefer Provider or other state management solutions for complex scenarios.

10

## Creating Adaptive Widgets

Adaptive widgets provide platform-appropriate experiences.

### Platform Adaptation

Make widgets that adapt to platform or size: use `Platform.isIOS` checks sparingly, prefer `Theme.of(context).platform` or conditional widgets that use Material or Cupertino components as required.

### Pattern: Adaptive Button

    Widget adaptiveButton(BuildContext context, String label, VoidCallback onPressed) {
      if (Theme.of(context).platform == TargetPlatform.iOS) {
        return CupertinoButton(child: Text(label), onPressed: onPressed);
      }
      return ElevatedButton(onPressed: onPressed, child: Text(label));
    }

11

## Accessibility Considerations in Styling

Accessible styling ensures your app works for all users.

### Accessibility Best Practices

- Ensure contrast ratio between text and background meets accessibility standards.
- Respect large font sizes by using relative sizing via `TextTheme` and `MediaQuery.textScaleFactor`.
- Provide semantic labels for interactive custom widgets and ensure tap targets meet minimum size.

12

## Theming Strategy and File Organization

Organizing theme files properly makes maintenance easier as your app grows.

### Suggested File Layout

- `lib/theme/app_theme.dart` — central ThemeData builder and toggles
- `lib/theme/app_colors.dart` — color tokens
- `lib/theme/typography.dart` — text styles and font families
- `lib/widgets/` — reusable custom widgets (ProfileCard, PrimaryButton)
- `lib/config/` — AppConfig or constants

Reason: Separation of concerns keeps style maintenance scalable as the app grows.

13

## Exercises

Practice what you've learned with these exercises:

### 1. App theme implementation

Create `AppTheme` file with light and dark ThemeData. Use a seed color and customize textTheme, elevatedButtonTheme, and inputDecorationTheme. Provide a toggle that switches theme at runtime using `Provider` or `ValueNotifier`.

### 2. Custom PrimaryButton widget

Implement `PrimaryButton` that reads colors from Theme and accepts `isLoading` to show a CircularProgressIndicator. Make it accessible with semantic labels.

### 3. Themed profile card

Reuse `ProfileCard` from earlier, but refactor it to use theme tokens for paddings, colors, and text styles. Add a `variant` parameter (compact, regular) to change spacing and font sizes.

### 4. Dark mode audit

Build a small screen with mixed content (images, text, icons) and switch between light/dark themes. Document any issues found (contrast, unreadable icons) and fix them.

14

## Session Assignment

Complete Exercises 1–3. Provide screenshots for light and dark themes, and include a short README describing theme tokens used, why they were chosen, and how to extend the theme for new components.