Skip to content

Color, UIColor, Color Literals, and Hex Colors in SwiftUI | SwiftUI Bootcamp #4

Getting colors right is the difference between an app that feels native and polished versus one that looks broken in dark mode. After this lesson you'll understand the four ways to specify color in SwiftUI — system colors, UIColor adaptives, color literals, and named asset colors — and know which to use in production.

What You'll Learn

  • The four color sources in SwiftUI: Color, Color(UIColor:), color literals, and named asset catalog colors
  • Why Color.primary and adaptive UIColor values automatically adapt to dark mode
  • How to create a named color in the Xcode asset catalog and reference it with Color("name")

Mental Model

Think of color sources as a spectrum from "opinionated and adaptive" to "precise and static." On one end, Color.primary says "use whatever color makes text readable right now" — it's black in light mode, white in dark mode, and you never have to think about it. In the middle, Color(UIColor.secondarySystemBackground) is an Apple-defined adaptive color that changes with the system theme. Further along, a named asset color is your custom definition stored in Xcode's asset catalog, where you can define a light-mode value and a dark-mode value independently. At the far end, Color(#colorLiteral(...)) and hex-derived colors are fully static — exactly one RGB value, no adaptation.

The lesson progresses from static to adaptive because that's how real production code evolves: start precise, then generalize for accessibility.

Detailed Explanation

SwiftUI's Color type is not the same as UIColor. Color is a SwiftUI-native value type designed for use in the declarative layout system. When you need colors from UIKit's extensive adaptive palette, you bridge them with Color(UIColor.someColor).

Color.primary is a semantic color — it picks black or white automatically based on the current color scheme. Other semantic system colors include Color.secondary, Color.accentColor, and the full Color(UIColor.systemBackground) family.

Color literals (#colorLiteral(red:green:blue:alpha:)) appear as colored swatches in Xcode, which makes them visually scannable in code. However, they encode raw RGB values with no adaptation — they look identical in light and dark mode, which is usually wrong for backgrounds and text, though fine for brand accent colors that intentionally stay constant.

Named asset colors (defined in Assets.xcassets) are the production best practice. You create a color set in the asset catalog with separate light and dark variants, then reference it by name anywhere in your code as Color("ColorName"). This is where you put your brand colors, since you can update them project-wide in one place.

Code Structure

The sample is in ColorsBootcamp.swift and uses a RoundedRectangle filled with Color("CustomColor") — a named color you define in the asset catalog. The commented-out alternatives show how you would use Color.primary and a color literal. The .shadow() modifier demonstrates using a color with opacity for soft shadows.

Complete Code

ColorsBootcamp.swift

swift
import SwiftUI

struct ColorsBootcamp: View {
    var body: some View {
        RoundedRectangle(cornerRadius: 25.0)
            .fill(
                //Color.primary          // adaptive: black in light mode, white in dark mode
                //Color(#colorLiteral(red: 0, green: 0.3285208941, blue: 0.5748849511, alpha: 1)) // static RGB literal (no dark mode adaptation)
                //Color(UIColor.secondarySystemBackground) // adaptive UIKit system color
                Color("CustomColor")   // named color from the Xcode asset catalog — define light and dark variants there
            )
            .frame(width: 300, height: 200)
            //.shadow(radius: 10) // simple shadow with default color and offset
            .shadow(color: Color("CustomColor").opacity(0.3), radius: 10, x: -20, y: -20) // colored shadow: same hue as fill but 30% opacity, offset top-left
    }
}

struct ColorsBootcamp_Previews: PreviewProvider {
    static var previews: some View {
        ColorsBootcamp()
            
            
            
    }
}

Code Walkthrough

  1. Color.primary (commented out) — This is the safest color for text and icons. It's automatically correct in every color scheme and accessibility mode. For backgrounds, use Color(UIColor.systemBackground) instead.
  2. Color(#colorLiteral(...)) — The long number sequence is an RGBA color literal. Xcode renders it as a colored square inline, but the actual value is static. Use these for brand colors that intentionally never change.
  3. Color(UIColor.secondarySystemBackground) — Bridges UIKit's adaptive color system into SwiftUI. secondarySystemBackground is a slightly off-white in light mode and a dark gray in dark mode — perfect for card backgrounds inside lists.
  4. Color("CustomColor") — Reads a named color from your Assets.xcassets file. To create one: open the asset catalog, click +, choose Color Set, name it CustomColor, then set light-mode and dark-mode swatches. The string must exactly match the asset name.
  5. .frame(width: 300, height: 200) — Gives the shape a concrete size so it doesn't expand to fill the screen.
  6. .shadow(color: Color("CustomColor").opacity(0.3), ...) — Uses the same named color but at 30% opacity for the shadow. This produces a tinted shadow that matches the card's color — a common design technique for vibrant, premium-feeling cards. x: -20, y: -20 offsets the shadow to the top-left.

Common Mistakes

Mistake: Using Color(#colorLiteral(...)) for backgrounds and expecting dark mode to work Color literals are static RGBA values. A light-gray background specified as a literal stays light gray in dark mode, making it invisible against dark backgrounds. Use named asset colors with separate dark-mode variants, or use semantic UIColor values bridged to SwiftUI.

Mistake: Misspelling the string in Color("CustomColor") If the string doesn't exactly match an asset catalog color name (case-sensitive), SwiftUI silently falls back to a clear/black color with no compiler error. Always double-check the spelling and add a comment with the asset name to help future maintainers.

Mistake: Applying .opacity() to a view modifier instead of to the color.opacity(0.3) on a View makes the entire view transparent, including its children. .opacity(0.3) called on a Color value only affects that color's alpha channel. For a semi-transparent shadow color, call .opacity() on the Color, not on the view.

Key Takeaways

  • Named asset catalog colors (with light and dark variants) are the production-correct way to define brand colors
  • Color.primary, Color.secondary, and Color(UIColor.systemBackground) are adaptive and should be preferred for text, icons, and backgrounds
  • Color literals are useful for precise design work or brand accents but don't adapt to dark mode

Last updated: June 27, 2026

Released under the MIT License.