Popular Searches
Popular Course Categories
Popular Courses

Supporting Light Mode, Dark Mode, and System Appearance

Supporting Light Mode, Dark Mode, and System Appearance

Apple Human Interface Guidelines and Advanced UI Components

Supporting Light Mode, Dark Mode, and System Appearance in iOS

Course: iOS App Development

Topic: Supporting Light Mode, Dark Mode, and System Appearance

Course Link: iOS App Development Course

Demo Link: Register for a Free Course Demo

Website: JustAcademy


1. Introduction

Modern iOS applications should provide a comfortable and consistent experience in different lighting conditions. Apple supports Light Mode and Dark Mode, allowing applications to adapt their colors, backgrounds, text, icons, and controls according to the user's appearance preference.

Light Mode generally uses light backgrounds and dark text, while Dark Mode uses dark backgrounds and lighter foreground elements. iOS can automatically switch between these appearances according to the user's system settings or schedule.

When developing applications with SwiftUI or UIKit, developers should understand how to support both appearances, use semantic system colors, respond to appearance changes, and test the interface for readability and accessibility.

2. Learning Objectives

  • Understand Light Mode, Dark Mode, and system appearance.
  • Learn how iOS determines the current interface style.
  • Use semantic colors and adaptive system backgrounds.
  • Support appearance changes in SwiftUI and UIKit.
  • Create custom colors that adapt to Light and Dark Mode.
  • Manage images, icons, borders, shadows, and text for both modes.
  • Test appearance behavior in the iOS Simulator and on real devices.
  • Identify common appearance-related problems and their solutions.

3. Understanding Light Mode and Dark Mode

3.1 Light Mode

Light Mode is an interface appearance that typically uses light backgrounds, dark text, and darker icons. It works well in bright environments and is familiar to many users.

Common characteristics:

  • White or light-colored backgrounds.
  • Dark text for readability.
  • Subtle separators and borders.
  • Icons and controls with sufficient contrast against light surfaces.

3.2 Dark Mode

Dark Mode uses darker backgrounds and lighter foreground elements. It can be more comfortable in low-light environments and can help maintain a consistent experience with the user's device appearance preference.

Common characteristics:

  • Dark or near-black backgrounds.
  • Light-colored text and icons.
  • Adaptive separators and elevated surfaces.
  • Colors adjusted to preserve contrast and visual hierarchy.

3.3 System Appearance

System Appearance allows the app to follow the appearance selected by the user in iOS Settings. When the user changes the device from Light to Dark Mode, an app that supports adaptive appearance can update automatically without requiring a manual refresh.

For most applications, following the system appearance is the recommended default because it respects user preferences and reduces unnecessary settings.

Feature Light Mode Dark Mode
Background Light or white Dark or near-black
Text Usually dark Usually light
Separators Adapted for light surfaces Adapted for dark surfaces
Images and icons Must remain visible on light backgrounds Must remain visible on dark backgrounds
Configuration User-selected or app-specific User-selected or app-specific

4. How iOS Appearance Adaptation Works

iOS provides appearance information through its interface environment. Frameworks such as SwiftUI and UIKit can use this information to choose suitable colors, images, and other visual properties.

There are three common approaches:

  1. Adaptive system styles: Use built-in colors and controls that automatically adapt to the current appearance.
  2. Custom adaptive resources: Define separate Light and Dark variants in an asset catalog.
  3. Explicit appearance configuration: Set a particular interface style for a screen or application when there is a genuine product requirement.

Whenever possible, prefer adaptive resources instead of checking the appearance manually for every visual element.

5. Supporting Appearance in SwiftUI

SwiftUI provides built-in support for adaptive colors and controls. Many standard views automatically respond to the environment's current color scheme.

5.1 Using System Colors

System colors such as .primary and .secondary adjust their appearance according to the environment. System background styles such as .background and .secondary can also help create adaptable interfaces.

import SwiftUI

struct AppearanceExampleView: View {
    var body: some View {
        VStack(spacing: 16) {
            Text("Welcome to My App")
                .font(.title)
                .foregroundStyle(.primary)

            Text("This text adapts to the current appearance.")
                .foregroundStyle(.secondary)

            Button("Continue") {
                print("Continue tapped")
            }
            .buttonStyle(.borderedProminent)
        }
        .padding()
        .frame(maxWidth: .infinity, maxHeight: .infinity)
        .background(Color(uiColor: .systemBackground))
    }
}

Explanation:

  • Color(uiColor: .systemBackground) uses a UIKit semantic background color that adapts to the current appearance.
  • .primary provides a prominent foreground style.
  • .secondary provides a less prominent foreground style.
  • The standard button style adapts to the platform's visual conventions.

In a SwiftUI-only interface, you can also use SwiftUI's semantic color styles and background materials. Avoid hardcoding white backgrounds and black text unless the design specifically requires fixed colors.

5.2 Reading the Current Color Scheme

Use the @Environment property wrapper to read the current color scheme when the interface needs to change behavior or display based on the active appearance.

import SwiftUI

struct ColorSchemeExampleView: View {
    @Environment(\.colorScheme) private var colorScheme

    var body: some View {
        VStack(spacing: 12) {
            Text("Appearance Demo")
                .font(.title)

            Text(
                colorScheme == .dark
                ? "Dark Mode is active"
                : "Light Mode is active"
            )
        }
        .padding()
    }
}

Explanation: The colorScheme environment value is either .light or .dark. SwiftUI updates the view when the environment changes, allowing the displayed message to stay current.

Use this technique when conditional behavior is genuinely necessary. For ordinary text and backgrounds, adaptive system colors are usually simpler and more maintainable.

5.3 Previewing Both Appearances in SwiftUI

Previews help developers check a view in both Light and Dark Mode without repeatedly changing the simulator's system settings.

import SwiftUI

#Preview("Light Mode") {
    AppearanceExampleView()
        .preferredColorScheme(.light)
}

#Preview("Dark Mode") {
    AppearanceExampleView()
        .preferredColorScheme(.dark)
}

The .preferredColorScheme(_:) modifier is useful for previews and for cases where a view intentionally needs a particular appearance. Use it carefully: applying it to a larger container can influence the appearance of the presentation or interface hierarchy.

6. Creating Custom Adaptive Colors

Applications often have brand colors, accent colors, status colors, and custom backgrounds. These colors should remain readable and visually appropriate in both appearances.

6.1 Using an Asset Catalog

Xcode asset catalogs allow you to create a color set with separate values for Light and Dark appearances.

  1. Open the project in Xcode.
  2. Open Assets.xcassets in the Project Navigator.
  3. Click the plus button and create a new Color Set.
  4. Give the color a meaningful name, such as AppSurface or BrandAccent.
  5. Select the color set and locate the appearance configuration in the Attributes Inspector.
  6. Choose the option that supports Any Appearance and Dark Appearance.
  7. Set the appropriate color value for each appearance.
  8. Build and preview the interface in both modes.

To use the named color in SwiftUI, reference the asset by its name.

import SwiftUI

struct CustomColorView: View {
    var body: some View {
        VStack(spacing: 12) {
            Text("Adaptive Brand Interface")
                .font(.title)

            Text("This surface uses a color asset.")
        }
        .padding()
        .background(Color("AppSurface"))
    }
}

Because the asset provides appearance-specific values, the color can update automatically when the active appearance changes.

6.2 Defining Adaptive Colors in Code

When needed, you can create a dynamic color using UIKit's UIColor trait-aware initializer and expose it to SwiftUI.

import SwiftUI
import UIKit

extension Color {
    static let adaptiveCardBackground = Color(
        UIColor { traits in
            traits.userInterfaceStyle == .dark
                ? UIColor(red: 0.12, green: 0.12, blue: 0.14, alpha: 1)
                : UIColor(red: 0.96, green: 0.96, blue: 0.98, alpha: 1)
        }
    )
}

struct AdaptiveCardView: View {
    var body: some View {
        Text("Adaptive Card")
            .padding()
            .background(Color.adaptiveCardBackground)
            .clipShape(RoundedRectangle(cornerRadius: 12))
    }
}

This approach can be useful when colors must be defined programmatically. For reusable design systems, named color assets are often easier for designers and developers to maintain together.

7. Supporting Light and Dark Mode in UIKit

UIKit provides dynamic system colors and a trait collection that describes the current environment, including the user interface style.

7.1 Using Dynamic System Colors

import UIKit

class AppearanceViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()

        view.backgroundColor = .systemBackground

        let titleLabel = UILabel()
        titleLabel.text = "Appearance Support"
        titleLabel.textColor = .label
        titleLabel.font = .boldSystemFont(ofSize: 24)
        titleLabel.translatesAutoresizingMaskIntoConstraints = false

        view.addSubview(titleLabel)

        NSLayoutConstraint.activate([
            titleLabel.centerXAnchor.constraint(
                equalTo: view.centerXAnchor
            ),
            titleLabel.centerYAnchor.constraint(
                equalTo: view.centerYAnchor
            )
        ])
    }
}

Important adaptive UIKit colors include:

Color Purpose
.systemBackground Primary background for a screen or surface.
.secondarySystemBackground Secondary grouped or inset surface.
.label Primary text color.
.secondaryLabel Secondary text color.
.separator Adaptive separator lines.
.systemBlue System blue accent color that adapts as designed by the platform.

7.2 Detecting Appearance Changes in UIKit

Most UIKit components using dynamic colors update automatically. If custom drawing or other appearance-dependent work must be refreshed, respond to trait changes.

import UIKit

class CustomAppearanceViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        updateCustomAppearance()
    }

    override func traitCollectionDidChange(
        _ previousTraitCollection: UITraitCollection?
    ) {
        super.traitCollectionDidChange(previousTraitCollection)

        if traitCollection.hasDifferentColorAppearance(
            comparedTo: previousTraitCollection
        ) {
            updateCustomAppearance()
        }
    }

    private func updateCustomAppearance() {
        if traitCollection.userInterfaceStyle == .dark {
            view.layer.borderColor = UIColor.white.cgColor
        } else {
            view.layer.borderColor = UIColor.black.cgColor
        }

        view.layer.borderWidth = 1
    }
}

Note: The trait-change APIs have evolved across iOS versions. For newer deployment targets, consult Apple's current UIKit documentation for the recommended trait registration and change-handling API. Avoid depending on a deprecated callback when a newer API is available.

8. Controlling the Interface Style

Most apps should follow the system setting. However, some apps may provide a user-facing appearance preference or require a fixed appearance for a particular experience.

8.1 SwiftUI Appearance Preference

SwiftUI's .preferredColorScheme(_:) modifier can request a specific color scheme for a view hierarchy or presentation.

// Follow the environment by default:
ContentView()

// Request Light Mode for a specific presentation:
ContentView()
    .preferredColorScheme(.light)

// Request Dark Mode for a specific presentation:
ContentView()
    .preferredColorScheme(.dark)

Do not apply a fixed scheme globally unless the app's requirements justify overriding the user's system preference.

8.2 UIKit Appearance Configuration

UIKit applications can configure the preferred interface style through the view controller or window appearance configuration. The exact setting depends on the intended scope and the project's iOS deployment target.

For an application-wide setting, the UIUserInterfaceStyle key in the app's Info configuration can be used where appropriate. Values commonly include Automatic, Light, and Dark.

Use Automatic when the app should follow the user's device appearance. Before forcing a style, consider whether the app truly needs to ignore the system setting.

9. Supporting Appearance for Images and Icons

Colors are only one part of an adaptive interface. Images, logos, illustrations, and icons must also remain clear in both appearances.

  • Use SF Symbols when suitable, because system symbols generally integrate well with native interface styling.
  • Use template rendering for monochrome icons when they should inherit a foreground tint.
  • Provide separate Light and Dark image variants in asset catalogs when an illustration requires different artwork.
  • Avoid transparent logos that disappear against a dark or light background.
  • Test image contrast, especially for illustrations with white backgrounds or thin lines.
import SwiftUI

struct IconExampleView: View {
    var body: some View {
        Label("Settings", systemImage: "gearshape")
            .font(.title3)
            .foregroundStyle(.primary)
            .padding()
    }
}

SF Symbols such as gearshape, magnifyingglass, and person.crop.circle can help create familiar native controls. Choose symbols that match the meaning of the action and test their appearance in context.

10. Handling Borders, Shadows, and Cards

Hardcoded borders and shadows may look attractive in Light Mode but become too strong, too subtle, or visually distracting in Dark Mode. Prefer system materials and adaptive colors where possible.

import SwiftUI

struct AdaptiveCard: View {
    var body: some View {
        VStack(alignment: .leading, spacing: 8) {
            Text("Course Details")
                .font(.headline)

            Text("Learn iOS app development step by step.")
                .foregroundStyle(.secondary)
        }
        .padding()
        .frame(maxWidth: .infinity, alignment: .leading)
        .background(.regularMaterial)
        .clipShape(RoundedRectangle(cornerRadius: 16))
    }
}

Materials such as .regularMaterial can provide a system-adaptive surface. Verify that the result remains readable over the content behind it and that it suits the app's visual design.

11. Accessibility and Readability

Appearance support must do more than change colors. It should preserve readability, meaning, and usability for all users.

  • Maintain sufficient contrast between text and its background.
  • Do not communicate status using color alone; use text, symbols, or other cues as well.
  • Check text at different Dynamic Type sizes.
  • Ensure disabled controls remain distinguishable without becoming unreadable.
  • Test charts, graphs, error messages, and form validation states in both modes.
  • Respect accessibility settings and platform conventions.

A color combination that works in one appearance may fail in the other. Review every important screen rather than assuming that automatic color adaptation guarantees a fully accessible design.

12. Testing Light Mode and Dark Mode in Xcode

  1. Open the iOS project in Xcode.
  2. Select an iOS Simulator device.
  3. Build and run the app.
  4. Open the simulator's Settings app and locate Display & Brightness.
  5. Switch between Light and Dark appearances.
  6. Return to the app and verify that its interface updates correctly.
  7. Repeat the test for every major screen, navigation flow, sheet, alert, and form.
  8. Check custom images, charts, buttons, text fields, separators, and disabled controls.
  9. Test on a real device when possible.

SwiftUI previews can also show the same view in both color schemes, helping developers identify visual problems early in development.

13. Common Problems and Solutions

Problem Likely Cause Solution
White background remains in Dark Mode A fixed white color is used. Use an adaptive system background or a color asset with appearance variants.
Text becomes invisible Foreground and background colors have insufficient contrast. Use semantic text colors and verify contrast in both appearances.
Logo disappears in Dark Mode The image was designed only for a light background. Provide an appropriate dark appearance asset or use a suitable template image.
Border is too bright or too dark A fixed border color is used. Use an adaptive color or update the border when the relevant traits change.
Some screens adapt but others do not Individual screens use hardcoded styles or fixed appearance settings. Audit reusable components and remove unnecessary fixed appearance overrides.
Appearance does not change as expected A color was converted to a non-dynamic value or custom drawing was not refreshed. Use dynamic resources and refresh custom-rendered content when traits change.

14. Best Practices

  • Follow the system appearance by default.
  • Use semantic colors instead of hardcoded black and white values.
  • Create named adaptive colors for brand-specific elements.
  • Use reusable components to maintain consistent appearance behavior.
  • Keep contrast, hierarchy, and readability consistent in both modes.
  • Provide appearance-specific assets when a single image is unsuitable.
  • Avoid forcing Light or Dark Mode without a clear product requirement.
  • Test alerts, sheets, menus, navigation bars, forms, and empty states.
  • Review third-party components because they may not adapt automatically.
  • Include Light Mode and Dark Mode checks in the regular quality assurance process.

15. Practical Exercise: Build an Adaptive Profile Screen

Goal: Create a profile screen that automatically adapts to Light Mode and Dark Mode.

Requirements:

  1. Create a SwiftUI view named AdaptiveProfileView.
  2. Add a profile icon, a user's display name, and a short description.
  3. Use semantic colors for primary and secondary text.
  4. Use a system or adaptive background for the main screen.
  5. Add a button with a native button style.
  6. Preview the screen in both Light and Dark Mode.
  7. Switch the simulator appearance and verify that all content remains readable.
import SwiftUI

struct AdaptiveProfileView: View {
    var body: some View {
        VStack(spacing: 16) {
            Image(systemName: "person.crop.circle.fill")
                .font(.system(size: 64))
                .foregroundStyle(.tint)

            Text("Alex Morgan")
                .font(.title2.bold())
                .foregroundStyle(.primary)

            Text("iOS App Developer")
                .foregroundStyle(.secondary)

            Button("Edit Profile") {
                print("Edit Profile tapped")
            }
            .buttonStyle(.borderedProminent)
        }
        .padding(24)
        .frame(maxWidth: .infinity, maxHeight: .infinity)
        .background(Color(uiColor: .systemBackground))
    }
}

#Preview("Light") {
    AdaptiveProfileView()
        .preferredColorScheme(.light)
}

#Preview("Dark") {
    AdaptiveProfileView()
        .preferredColorScheme(.dark)
}

Expected result: The profile screen should remain readable and visually consistent when the appearance changes, without requiring separate screen implementations.

16. Interview Questions and Answers

Q1. What is Light Mode in iOS?

Light Mode is an interface appearance that generally uses light backgrounds and darker foreground elements.

Q2. What is Dark Mode?

Dark Mode uses darker surfaces and lighter foreground elements to provide an alternative interface appearance.

Q3. What does system appearance mean?

It means the app follows the appearance selected by the user or system, allowing supported interface elements to adapt automatically.

Q4. How do you read the current appearance in SwiftUI?

Use @Environment(\.colorScheme) to read whether the current color scheme is light or dark.

Q5. Why should developers avoid hardcoded white backgrounds?

A hardcoded white background may remain white in Dark Mode, creating poor contrast or an inconsistent interface. Adaptive system colors or color assets are usually better.

Q6. What are semantic colors?

Semantic colors are colors chosen for their interface role, such as primary text, secondary text, or system background, rather than for a fixed RGB value.

Q7. How can you create custom colors for both appearances?

Create a Color Set in the Xcode asset catalog and provide appropriate values for Light and Dark appearances.

Q8. How do you test Dark Mode in Xcode?

Switch the iOS Simulator's appearance in Display & Brightness settings or use SwiftUI previews configured for light and dark color schemes.

Q9. Should every app force Dark Mode?

No. Most apps should respect the user's system preference unless a specific product requirement justifies a fixed appearance.

Q10. What should be checked besides text and backgrounds?

Check icons, images, borders, shadows, charts, navigation elements, controls, alerts, sheets, and accessibility contrast.

17. Quick Revision Checklist

  • Light Mode and Dark Mode provide alternative interface appearances.
  • System appearance lets an app follow the user's preference.
  • SwiftUI supports adaptive appearance through environment values and system styles.
  • UIKit provides dynamic system colors and trait information.
  • Asset catalogs can store appearance-specific colors and images.
  • Semantic colors help preserve contrast and consistency.
  • Custom drawing may need to respond to appearance or trait changes.
  • Test the entire application in both appearances before release.

18. Conclusion

Supporting Light Mode, Dark Mode, and system appearance is an important part of creating polished iOS applications. By using semantic colors, adaptive asset catalogs, system styles, and appearance-aware components, developers can create interfaces that respect user preferences and remain readable across different environments.

For most applications, the best starting point is to follow the system appearance, avoid unnecessary fixed colors, and test every important screen in both modes. These practices make SwiftUI and UIKit applications easier to maintain, more accessible, and more consistent with native iOS design conventions.

Additional Learning Resources

Previous Next
whatsapp