Hierarchical Styling: My Clean Approach to SwiftUI Design


When working with SwiftUI, I recently discovered an elegant feature of hierarchical styles that changed how I approach design implementation. Instead of directly appending styles to a ShapeStyle, we can leverage the parent's .tint property for more flexible and maintainable styling.

The Traditional Approach

Let's start with a basic example - a Form containing a single Label:

Form {
  Label("Battery charging options", systemImage: "battery.100percent")
}

By default, this produces a simple, un-styled view:

While functional, I wanted to recreate something closer to Apple's Settings app, where icons are beautifully wrapped in coloured rounded rectangles.

I particularly appreciate using varying opacities of a single colour to create harmonious, complementary styles.

First Attempt: Direct Colour Assignment

My initial solution looked like this:

struct SettingsFormLabel: LabelStyle {
  let foregroundColor: any ShapeStyle
  let backgroundColor: any ShapeStyle

  func makeBody(configuration: Configuration) -> some View {
    Label {
      configuration.title
    } icon: {
      configuration.icon
        .foregroundStyle(AnyShapeStyle(foregroundColor))
        .background {
          RoundedRectangle(cornerRadius: 6)
            .fill(AnyShapeStyle(backgroundColor))
            .frame(width: 36, height: 36)
        }
    }
  }
}

To use this style, you would write:

Form {
  Label("Battery charging options", systemImage: "battery.100percent")
    .labelStyle(SettingsFormLabel(
      foregroundColor: Color.green,
      backgroundColor: .green.quinary
    ))
}

This produces a styled view with our green theme:

While this works, it requires specifying both foreground and background colours every time you use the label style. This approach can become tedious and harder to maintain across larger applications.

A Better Way: Hierarchical Styling

Here's where hierarchical styling shines. By leveraging SwiftUI's built-in .tint modifier, we can create a more elegant solution:

struct SettingsFormLabel: LabelStyle {
  func makeBody(configuration: Configuration) -> some View {
    Label {
      configuration.title
    } icon: {
      configuration.icon
        .foregroundStyle(.primary)  // <-- here
        .background {
          RoundedRectangle(cornerRadius: 6)
            .fill(.quinary)        // <-- here
            .frame(width: 36, height: 36)
        }
    }
  }
}

Form {
  Label("Battery charging options", systemImage: "battery.100percent")
    .labelStyle(SettingsFormLabel())
}
.tint(.green)

This approach offers several advantages:

  • Cleaner, more maintainable code
  • Single point of colour control through the .tint modifier
  • Easier to make app-wide colour scheme changes

The end result looks identical, but the code is more elegant and easier to maintain. By using hierarchical styling, we've simplified our implementation while maintaining full control over the visual design.

It's important to note that the .tint needs to be on the Form - which affects all the .labelStyles. As noted by Michael Freiwald you need to use .foregroundStyle to change them on the Label directly.


Enjoyed this content? Fuel my creativity!

If you found this article helpful, you can keep the ideas flowing by supporting me. Buy me a coffee or check out my apps to help me create more content like this!

Coffee Check out my apps