Swift and SwiftUI tutorials for Swift Developers

Label in SwiftUI

In the vast ecosystem of SwiftUI, there are components that, at first glance, seem redundant. Why do we need a Labelcomponent if we already have Text and Image, and we can easily combine them inside an HStack?

This is a question many developers ask when starting out. The short answer is: Semantics and Adaptability.

Label is not just a visual container; it is a semantic structure that describes a piece of the user interface consisting of a title and an icon. By using Label, you are giving the operating system the freedom to render that content in the most appropriate way depending on the context (a list, a toolbar, a context menu, or a screen reader).

In this tutorial, we will break down every aspect of Label in SwiftUI, from its most basic implementation to creating complex, custom style systems for iOS, macOS, and watchOS.


1. What is Label and why should you use it?

Label is a standard view introduced in SwiftUI 2.0 (iOS 14). Its purpose is to unify descriptive text and a representative image into a single cohesive element.

The HStack Problem

Before Label, we used to do this:

HStack {
    Image(systemName: "folder")
    Text("My Files")
}

While this works visually on the canvas, it has downsides:

  1. Accessibility: VoiceOver reads “Image, folder” and then “My Files.” They are not intrinsically linked.
  2. Context: If you put this HStack inside a toolbar (ToolbarItem), the system might not know how to format it correctly.
  3. Menus: In iOS context menus, an HStack does not automatically align the icon to the right as the native system does.

The Solution with Label

Label("My Files", systemImage: "folder")

With this simple line, you gain automatic Dynamic Type alignment (text and icon scale together), native support in Menus and Lists, and a better accessibility experience.


2. Basic Initializers: Creating Your First Label

SwiftUI offers several ways to instantiate a Label, depending on the source of your data.

A. Text and SF Symbols (Most Common)

The fastest way to prototype and build native interfaces is by using SF Symbols.

Label("Settings", systemImage: "gear")

Note: The icon and text align automatically on the baseline.

B. Text and Asset Image

If you have a custom icon in your Assets.xcassets catalog:

Label("My Profile", image: "custom_user_icon")

Important: Make sure your image in Assets is configured as “Render As: Template Image” if you want SwiftUI to be able to change its color dynamically.

C. The Flexible Initializer (Closures)

Sometimes, the title isn’t just a String, or the image isn’t just a static icon. Perhaps the icon is a complex view or an emoji. For this, we use the constructor with closures:

Label {
    VStack(alignment: .leading) {
        Text("John Doe")
            .font(.headline)
        Text("iOS Developer")
            .font(.caption)
            .foregroundColor(.secondary)
    }
} icon: {
    Image("profile_photo")
        .resizable()
        .scaledToFill()
        .frame(width: 40, height: 40)
        .clipShape(Circle())
        .overlay(Circle().stroke(Color.blue, lineWidth: 2))
}

This approach turns Label into a superpowered container, maintaining the “Title + Icon” semantics but allowing total visual freedom.


3. Styling Labels: The .labelStyle Modifier

This is where Label shines over HStack. SwiftUI allows us to separate content from presentation.

Imagine you have the same label in a list (where you want to see both icon and text) and in a toolbar (where you might only want the icon due to space constraints). With HStack, you would have to use if/else. With Label, you use styles.

Built-in Styles

SwiftUI comes with several predefined styles that adapt to Apple’s standards:

  1. DefaultLabelStyle (or .automatic): The default behavior. Shows icon and title, unless the container (like a Toolbar) dictates otherwise.
  2. IconOnlyLabelStyle: Hides the text and shows only the icon. Ideal for compact button bars.
Label("Search", systemImage: "magnifyingglass")
    .labelStyle(.iconOnly)
  1. Accessibility Note: Although the text is visually hidden, VoiceOver still reads it. It’s perfect!
  2. TitleOnlyLabelStyle: Hides the icon and shows only the text.

Cascading Styles

You can apply a style to a parent container, and all Labels inside will inherit that style.

VStack {
    Label("One", systemImage: "1.circle")
    Label("Two", systemImage: "2.circle")
    Label("Three", systemImage: "3.circle")
}
.labelStyle(.iconOnly) // All labels become icons

4. Advanced Customization: Creating Your Own LabelStyle

Default styles are useful but limited. What if you want the icon above the text? Or what if you want the icon to have a background color?

For this, we create a custom style by conforming to the LabelStyle protocol.

The Anatomy of LabelStyle

The protocol requires a function makeBody(configuration:). The configuration gives us access to two magic properties:

  • configuration.title: The title view.
  • configuration.icon: The icon view.

Tutorial: Creating a “Vertical Button” Style

Let’s create a common style found in Dashboards: a large icon with text underneath.

struct VerticalLabelStyle: LabelStyle {
    func makeBody(configuration: Configuration) -> some View {
        VStack(spacing: 8) {
            configuration.icon
                .font(.system(size: 32)) // Scale the icon
                .foregroundColor(.blue)
            
            configuration.title
                .font(.caption)
                .fontWeight(.bold)
                .foregroundColor(.primary)
        }
        .padding()
        .background(Color.blue.opacity(0.1))
        .cornerRadius(10)
    }
}

How to Use Your Custom Style

Now you can apply this style cleanly in your view:

HStack {
    Label("Wi-Fi", systemImage: "wifi")
    Label("Bluetooth", systemImage: "wave.3.right")
    Label("Data", systemImage: "antenna.radiowaves.left.and.right")
}
.labelStyle(VerticalLabelStyle()) // Apply our struct

To make it even more “SwiftUI-native,” we can extend LabelStyle:

extension LabelStyle where Self == VerticalLabelStyle {
    static var verticalCard: VerticalLabelStyle { VerticalLabelStyle() }
}

// Final usage:
Label("Wi-Fi", systemImage: "wifi")
    .labelStyle(.verticalCard)

5. Changing Colors and Images

Color customization in Label has improved significantly with recent iOS versions.

Simple Colors

If you apply .foregroundStyle(.red) to a Label, both the text and the icon will be tinted red (unless you use a custom style that overrides this).

Label("Error", systemImage: "exclamationmark.triangle")
    .foregroundStyle(.red)

Multicolor in SF Symbols

SF Symbols supports multicolor rendering. Label automatically respects this.

Label("Weather", systemImage: "cloud.sun.rain.fill")
    .symbolRenderingMode(.multicolor)

This will show the white cloud, yellow sun, and blue rain, all within your label.

Coloring Parts Individually

If you want the icon to be green and the text black, you have two options:

Option A: Closure Initializer

Label {
    Text("Success").foregroundStyle(.black)
} icon: {
    Image(systemName: "checkmark.circle.fill").foregroundStyle(.green)
}

Option B: Custom LabelStyle (More Reusable)

struct ColoredIconStyle: LabelStyle {
    var iconColor: Color
    
    func makeBody(configuration: Configuration) -> some View {
        HStack {
            configuration.icon.foregroundStyle(iconColor)
            configuration.title.foregroundStyle(.primary)
        }
    }
}

// Usage:
Label("Verified", systemImage: "checkmark.seal")
    .labelStyle(ColoredIconStyle(iconColor: .blue))

6. Platform Differences: iOS vs. macOS vs. watchOS

SwiftUI strives for “Write once,” but behavior adapts.

iOS and iPadOS

  • In a List (InsetGrouped style), the Label icon doesn’t show with the system accent color by default, but it aligns perfectly to the left.
  • In a Menu (context menus), iOS automatically moves the icon to the right side of the text, ignoring your code order to comply with Apple’s design guidelines. Using Label is the only way to get this native behavior.

macOS

  • Sidebars in macOS rely heavily on Label.
  • In macOS, Label icons in toolbars often behave like borderless buttons.
  • The default font size is different.

watchOS

  • On Apple Watch, horizontal space is gold. Labels in lists are often used to leverage vertical space if the text is long.
  • Using .labelStyle(.iconOnly) is very common in complications or quick action buttons.

7. Practical Use Cases and Tricks

The Settings List Trick

A classic iOS design is the Settings list: an icon with a background color inside a rounded square, with text next to it. Let’s create a style for this.

struct SettingsLabelStyle: LabelStyle {
    var backgroundColor: Color
    
    func makeBody(configuration: Configuration) -> some View {
        HStack {
            configuration.icon
                .foregroundColor(.white)
                .frame(width: 28, height: 28)
                .background(backgroundColor)
                .cornerRadius(6)
            
            configuration.title
                .font(.body)
        }
    }
}

// Usage example
List {
    Label("Airplane Mode", systemImage: "airplane")
        .labelStyle(SettingsLabelStyle(backgroundColor: .orange))
    
    Label("Wi-Fi", systemImage: "wifi")
        .labelStyle(SettingsLabelStyle(backgroundColor: .blue))
}

Animations (iOS 17+)

With the arrival of iOS 17, Label supports new Symbol Effects.

@State private var isLoading = false

Label("Syncing", systemImage: "arrow.triangle.2.circlepath")
    .symbolEffect(.pulse, isActive: isLoading)
    .onTapGesture {
        isLoading.toggle()
    }

This will cause the icon to pulse gently without you having to write complex animation logic.


8. Accessibility: The Hidden Power of Label

As mentioned at the beginning, accessibility is a key reason to use Label.

If you use an HStack with an image and text, VoiceOver might read: “Image, right arrow, Next”. This is redundant.

By using Label("Next", systemImage: "arrow.right"):

  1. If the style is .titleOnly, VoiceOver reads “Next”.
  2. If the style is .iconOnly, VoiceOver reads “Next” (using the title text as the accessibility label for the icon).
  3. If standard, the system groups the elements so they are perceived as a single interactive action.

Pro Tip: If you use a custom decorative image inside a Label, the system is usually smart about it, but it’s always good to verify.


Conclusion

The Label component in SwiftUI is a perfect example of the framework’s philosophy: simple on the outside, powerful on the inside.

Moving from using manual HStacks to Label will not only clean up your code, making it more readable and maintainable, but it will also automatically improve the user experience in terms of accessibility and design consistency across the entire Apple ecosystem.

Summary of steps to master Label:

  1. Always use it when you have a Title + Icon pair.
  2. Use closure initializers for complex layouts.
  3. Create your own LabelStyles to reuse designs throughout your app.
  4. Leverage system modifiers like .symbolVariant and .symbolRenderingMode to bring your icons to life.

If you have any questions about this article, please contact me and I will be happy to help you 🙂. You can contact me on my X profile or on my Instagram profile.

Leave a Reply

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

Previous Article

ZStack in SwiftUI explained with examples

Next Article

Best AI for SwiftUI

Related Posts