# How I Theme My Flutter Apps: Introducing "Skinning"

Last year, I wrote two articles about theming in Flutter, sharing my frustrations with `ThemeData` ([part 1](https://seg.veenstra.dev/why-theming-in-flutter-is-confusing-to-me) & [part 2](https://seg.veenstra.dev/why-theming-in-flutter-is-confusing-to-me-part-2)). While `ThemeData` is powerful, its extensive properties can be overwhelming, especially when many of them are rarely used. After experimenting with a more intuitive approach, I developed a method called "Skinning." This method simplifies theming by focusing on the essential properties that change based on the device’s theme (light or dark mode) while coupling behavior and styling in custom widgets.

In this article, I’ll walk you through what Skinning is, why I prefer it over `ThemeData`, and how I implement it in my apps.

---

### The Problem with ThemeData

When I first started using `ThemeData`, I found it bloated with hundreds of properties that were unnecessary for my projects. I often only needed to change a handful of colors or styles between light and dark modes. Trying to manage those changes through `ThemeData` made the theming process feel overly complicated. Additionally, `ThemeData` is primarily concerned with styling individual components, but what if you want to adjust both the behavior and styling in a unified way?

### My Solution: "Skinning"

Instead of directly using Flutter’s components like `ElevatedButton`, `AppBar`, or `Scaffold`, I wrap them in custom widgets that encapsulate both behavior and appearance. This way, I have full control over how my components look and function, making global changes easier and cleaner.

#### Custom Widgets for Unified Behavior and Theming

For instance, when I need a primary button, I don’t use an `ElevatedButton` directly. Instead, I create a `PrimaryButton` widget that wraps the `ElevatedButton` internally, allowing me to manage both behavior and styling in one place.

```dart
class PrimaryButton extends StatelessWidget {
  final String label;
  final VoidCallback onPressed;

  const PrimaryButton({required this.label, required this.onPressed});

  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      onPressed: onPressed,
      style: ButtonStyle(
        backgroundColor: WidgetStateProperty.all(context.skin.primaryColor), // Using Skin for color
      ),
      child: Text(label),
    );
  }
}
```

This makes it easy to update all primary buttons by just modifying the `PrimaryButton` class. If I need to switch to an `OutlinedButton`, I do it once in the `PrimaryButton` class, and the change applies app-wide.

#### Skin: A Minimalist Alternative to ThemeData

To handle theming across light and dark modes, I use a `SkinProvider`, an `InheritedWidget` that provides two `Skin` objects: `lightSkin` and `darkSkin`. The app automatically switches between these based on the device’s theme configuration.

Here’s how the `SkinProvider` works:

```dart
class SkinProvider extends InheritedWidget {
  final Skin lightSkin;
  final Skin darkSkin;

  const SkinProvider({
    required this.lightSkin,
    required this.darkSkin,
    required Widget child,
  }) : super(child: child);

  static Skin of(BuildContext context) {
    final brightness = MediaQuery.of(context).platformBrightness;
    final provider = context.dependOnInheritedWidgetOfExactType<SkinProvider>()!;
    return brightness == Brightness.dark ? provider.darkSkin : provider.lightSkin;
  }

  @override
  bool updateShouldNotify(SkinProvider oldWidget) {
    return lightSkin != oldWidget.lightSkin || darkSkin != oldWidget.darkSkin;
  }
}
```

And here’s a basic `Skin` class:

```dart
class Skin {
  final Color primaryColor;
  final Color secondaryColor;

  Skin({
    required this.primaryColor,
    required this.secondaryColor,
  });
}
```

Instead of managing a large `ThemeData` object, I focus only on the properties that need to change between light and dark modes. In this example, I only define `primaryColor` and `secondaryColor` in each `Skin`, keeping it simple.

#### Extension Method for Easy Access

To make accessing the current `Skin` easier, I use an extension method on `BuildContext`. This allows me to quickly get the active theme in any widget with `context.skin`:

```dart
extension SkinExtension on BuildContext {
  Skin get skin => SkinProvider.of(this);
}
```

Now, in my custom widgets, I can easily access the current skin:

```dart
style: ButtonStyle(
  backgroundColor: WidgetStateProperty.all(context.skins.primaryColor),
),
```

This makes it easy to theme components based on the active light or dark mode.

---

### Why I Use Skinning

* **Unified Behavior and Theming:** By creating custom widgets like `PrimaryButton`, I can manage both the appearance and behavior of my UI elements in one place. This keeps my code DRY and simplifies maintenance.
    
* **Automatic Theme Switching:** By providing both `lightSkin` and `darkSkin` via the `SkinProvider`, my app automatically switches between the two based on the device’s light or dark mode configuration. No extra logic is needed in each component to handle the mode switching.
    
* **Simplicity:** Instead of dealing with the complexity of `ThemeData`, I focus on a leaner `Skin` class that includes only the properties I need. This keeps the theming logic clear and easy to manage.
    
* **Flexibility:** When a design change happens, like switching all primary buttons from `ElevatedButton` to `OutlinedButton`, I only need to modify the `PrimaryButton` class. This change propagates across the entire app, reducing the risk of inconsistencies.
    

---

### Conclusion

Skinning offers a simpler, more flexible approach to theming Flutter apps. By wrapping components in custom widgets and using a `SkinProvider` to manage both `lightSkin` and `darkSkin`, I can easily switch themes based on device configuration while keeping behavior and appearance tightly coupled.

This method has streamlined my theming process and allowed me to focus on what really matters: the specific styles and behaviors my app needs. If you’re looking for a more intuitive way to theme your Flutter apps, Skinning might be just what you need.
