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:
- Accessibility: VoiceOver reads “Image, folder” and then “My Files.” They are not intrinsically linked.
- Context: If you put this
HStackinside a toolbar (ToolbarItem), the system might not know how to format it correctly. - Menus: In iOS context menus, an
HStackdoes 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:
DefaultLabelStyle(or.automatic): The default behavior. Shows icon and title, unless the container (like a Toolbar) dictates otherwise.IconOnlyLabelStyle: Hides the text and shows only the icon. Ideal for compact button bars.
Label("Search", systemImage: "magnifyingglass")
.labelStyle(.iconOnly)- Accessibility Note: Although the text is visually hidden, VoiceOver still reads it. It’s perfect!
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 icons4. 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 structTo 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(InsetGroupedstyle), 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. UsingLabelis 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"):
- If the style is
.titleOnly, VoiceOver reads “Next”. - If the style is
.iconOnly, VoiceOver reads “Next” (using the title text as the accessibility label for the icon). - 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:
- Always use it when you have a Title + Icon pair.
- Use
closureinitializers for complex layouts. - Create your own
LabelStyles to reuse designs throughout your app. - Leverage system modifiers like
.symbolVariantand.symbolRenderingModeto 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.