Text Colors, Dark Mode & Wide Color
Use when choosing text colors, semantic colors, dark-mode behavior, wide-color or HDR text, or making attributed-text colors adapt correctly across UIKit, AppKit, and SwiftUI. Reach for this when the problem is specifically text color behavior, not broader formatting or typography.
Use when choosing text colors, semantic colors, dark-mode behavior, wide-color or HDR text, or making attributed-text colors adapt correctly across UIKit, AppKit, and SwiftUI. Reach for this when the problem is specifically text color behavior, not broader formatting or typography.
Family: Rich Text And Formatting
Use this skill when the main question is how text color should adapt across UIKit, AppKit, SwiftUI, dark mode, or wide-color environments.
When to Use
Section titled “When to Use”- You need semantic text colors.
- You are debugging dark-mode text issues.
- You need to know whether wide color or HDR applies to text rendering.
Quick Decision
Section titled “Quick Decision”- Need adaptive body text colors -> use semantic label/text colors
- Need attributed text colors that survive mode changes -> resolve dynamic colors correctly
- Need advanced formatting attributes ->
/skill apple-text-formatting-ref
Core Guidance
Section titled “Core Guidance”UIKit Semantic Text Colors (Complete)
Section titled “UIKit Semantic Text Colors (Complete)”Label Hierarchy
Section titled “Label Hierarchy”| Color | Light Mode | Dark Mode | Use For |
|---|---|---|---|
.label | Black (a1.0) | White (a1.0) | Primary text |
.secondaryLabel | #3C3C43 (a0.6) | #EBEBF5 (a0.6) | Secondary text |
.tertiaryLabel | #3C3C43 (a0.3) | #EBEBF5 (a0.3) | Tertiary text |
.quaternaryLabel | #3C3C43 (a0.18) | #EBEBF5 (a0.18) | Disabled/hint text |
.placeholderText | #3C3C43 (a0.3) | #EBEBF5 (a0.3) | Placeholder in fields |
.link | #007AFF | #0984FF | Tappable links |
Legacy (NON-Dynamic — Avoid)
Section titled “Legacy (NON-Dynamic — Avoid)”| Color | Value | Problem |
|---|---|---|
.lightText | White (a0.6) — always | Does NOT adapt to dark mode |
.darkText | Black (a1.0) — always | Does NOT adapt to dark mode |
Always use .label instead of .darkText, .lightText, or hardcoded black/white.
System Tint Colors (Adaptive)
Section titled “System Tint Colors (Adaptive)”All shift slightly between light and dark for contrast:
| Color | Light | Dark |
|---|---|---|
.systemBlue | #007AFF | #0A84FF |
.systemRed | #FF3B30 | #FF453A |
.systemGreen | #34C759 | #30D158 |
.systemOrange | #FF9500 | #FF9F0A |
.systemPurple | #AF52DE | #BF5AF2 |
.systemPink | #FF2D55 | #FF375F |
.systemYellow | #FFCC00 | #FFD60A |
.systemIndigo | #5856D6 | #5E5CE6 |
.systemTeal | #5AC8FA | #64D2FF |
.systemCyan | #32ADE6 | #64D2FF |
.systemMint | #00C7BE | #63E6E2 |
.systemBrown | #A2845E | #AC8E68 |
AppKit Semantic Text Colors
Section titled “AppKit Semantic Text Colors”Critical: .textColor vs .labelColor
Section titled “Critical: .textColor vs .labelColor”| Color | Alpha | Vibrancy | Use For |
|---|---|---|---|
.textColor | 1.0 (fully opaque) | ❌ No | Document body text, NSTextView content |
.labelColor | ~0.85 | ✅ Yes | UI labels, buttons, sidebar items |
On macOS, .textColor is the right default for text views. .labelColor is for UI chrome. They look similar but behave differently with vibrancy.
Full macOS Text Color Catalog
Section titled “Full macOS Text Color Catalog”| Color | Light | Dark | Use For |
|---|---|---|---|
.textColor | Black (a1.0) | White (a1.0) | Body text |
.textBackgroundColor | White (a1.0) | #1E1E1E (a1.0) | Text view background |
.selectedTextColor | White | White | Selected text foreground |
.selectedTextBackgroundColor | #0063E1 | #0050AA | Selection highlight |
.unemphasizedSelectedTextColor | Black | White | Selection (window inactive) |
.unemphasizedSelectedTextBackgroundColor | #DCDCDC | #464646 | Selection bg (inactive) |
.placeholderTextColor | Black (a0.25) | White (a0.25) | Placeholder text |
.headerTextColor | Black (a0.85) | White (a1.0) | Section headers |
.linkColor | #0068DA | #419CFF | Hyperlinks |
.controlTextColor | Black (a0.85) | White (a0.85) | Control labels |
.disabledControlTextColor | Black (a0.25) | White (a0.25) | Disabled controls |
SwiftUI Semantic Colors
Section titled “SwiftUI Semantic Colors”Text Foreground Styles
Section titled “Text Foreground Styles”Text("Primary").foregroundStyle(.primary) // ≈ .labelText("Secondary").foregroundStyle(.secondary) // ≈ .secondaryLabelText("Tertiary").foregroundStyle(.tertiary) // ≈ .tertiaryLabelBridging UIKit/AppKit Colors
Section titled “Bridging UIKit/AppKit Colors”// Use UIKit semantic colors in SwiftUIText("Label").foregroundStyle(Color(uiColor: .label))Text("Link").foregroundStyle(Color(uiColor: .link))
// macOSText("Text").foregroundStyle(Color(nsColor: .textColor))SwiftUI Color Literals
Section titled “SwiftUI Color Literals”Color.primary // Adaptive: black in light, white in darkColor.secondary // Adaptive: with reduced opacityColor.accentColor // App tint color (default: .blue)Dark Mode Adaptation
Section titled “Dark Mode Adaptation”What Auto-Adapts
Section titled “What Auto-Adapts”| Scenario | Auto-adapts? |
|---|---|
UILabel with .textColor = .label | ✅ |
| UITextView with no explicit color | ✅ (defaults to .label in TextKit 2) |
NSAttributedString with .foregroundColor: UIColor.label | ✅ UIKit re-resolves at draw time |
NSAttributedString with .foregroundColor: UIColor.red (hardcoded) | ❌ Stays red always |
NSAttributedString with .foregroundColor: UIColor.systemRed | ✅ Shifts between light/dark variants |
SwiftUI Text with .foregroundStyle(.primary) | ✅ |
SwiftUI Text with .foregroundStyle(Color.red) | ✅ (Color.red is adaptive) |
| CALayer.borderColor (CGColor) | ❌ Must update manually |
The Default Foreground Color Trap
Section titled “The Default Foreground Color Trap”UIKit default attributed string foreground is black, NOT .label.
// ❌ This text will be invisible in dark modelet str = NSAttributedString(string: "Hello") // foreground defaults to .blacktextView.attributedText = str
// ✅ Always set foreground color explicitlylet str = NSAttributedString(string: "Hello", attributes: [ .foregroundColor: UIColor.label])Making Attributed String Colors Dynamic
Section titled “Making Attributed String Colors Dynamic”Use semantic UIColors — they auto-resolve:
let attrs: [NSAttributedString.Key: Any] = [ .foregroundColor: UIColor.label, // ✅ Adapts .backgroundColor: UIColor.systemYellow, // ✅ Adapts]Do NOT use:
.foregroundColor: UIColor(red: 0, green: 0, blue: 0, alpha: 1) // ❌ Always black.foregroundColor: UIColor.black // ❌ Always blackCustom Dynamic Colors
Section titled “Custom Dynamic Colors”let adaptiveColor = UIColor { traitCollection in switch traitCollection.userInterfaceStyle { case .dark: return UIColor(red: 0.9, green: 0.9, blue: 1.0, alpha: 1.0) default: return UIColor(red: 0.1, green: 0.1, blue: 0.2, alpha: 1.0) }}High Contrast (Increase Contrast)
Section titled “High Contrast (Increase Contrast)”Semantic colors also adapt to the Increase Contrast accessibility setting:
let color = UIColor { traitCollection in if traitCollection.accessibilityContrast == .high { return .black // Higher contrast variant } else { return UIColor(white: 0.3, alpha: 1.0) }}Apple’s built-in semantic colors already handle this — .label becomes pure black/white in high contrast.
Responding to Trait Changes
Section titled “Responding to Trait Changes”// iOS 17+ (preferred)registerForTraitChanges([UITraitUserInterfaceStyle.self]) { (self: Self, _) in self.updateColors()}
// iOS 13-16override func traitCollectionDidChange(_ previous: UITraitCollection?) { super.traitCollectionDidChange(previous) if traitCollection.hasDifferentColorAppearance(comparedTo: previous) { updateColors() }}Wide Color / Display P3
Section titled “Wide Color / Display P3”Creating P3 Colors for Text
Section titled “Creating P3 Colors for Text”// UIKitlet p3Color = UIColor(displayP3Red: 1.0, green: 0.1, blue: 0.1, alpha: 1.0)
// AppKitlet p3Color = NSColor(displayP3Red: 1.0, green: 0.1, blue: 0.1, alpha: 1.0)
// SwiftUIlet p3Color = Color(.displayP3, red: 1.0, green: 0.1, blue: 0.1, opacity: 1.0)
// Note: SwiftUI Color(red:green:blue:) defaults to sRGB, NOT P3Does Text Actually Render in P3?
Section titled “Does Text Actually Render in P3?”Yes, on P3 displays. Text rendering goes through Core Text → Core Graphics, which renders in the color space of the CGContext. On P3 displays (iPhone 7+, iPad Pro, Mac with P3 display), the backing CALayer uses a P3 color space, so text colors outside sRGB gamut are rendered correctly.
Practical impact for text: Minimal. Text readability depends on contrast, not gamut. P3 colors that are more saturated than sRGB may reduce readability. Use P3 for branding colors on text, not for body copy.
HDR / EDR for Text
Section titled “HDR / EDR for Text”Can Text Be HDR?
Section titled “Can Text Be HDR?”Not through the standard text APIs in a normal, supported way. Standard text views (UILabel, UITextView, NSTextView, SwiftUI Text) are not the usual HDR rendering path and should be treated as SDR text for UI design.
Apple’s EDR guidance treats 1.0 as reference/UI white, and most UI should not exceed that. Pushing text above it tends to create a glowing look that hurts readability.
What About allowedDynamicRange(.high)?
Section titled “What About allowedDynamicRange(.high)?”SwiftUI does expose allowedDynamicRange as a view environment using Image.DynamicRange, but Apple’s HDR APIs and examples are centered on image/video/custom rendering content, not standard text as a recommended HDR workflow.
Possible Workarounds
Section titled “Possible Workarounds”- Custom Metal/CAMetalLayer rendering using public Core Animation and Metal APIs
- Private CAFilter APIs (not App Store safe)
- Core Image or other custom compositing pipelines
The first category can be App Store-safe if it stays on documented APIs, but it is custom graphics work, not standard text rendering. Treat it as a special effect path, not normal body text/UI text design.
Bottom line: HDR text is not a standard or recommended text treatment for regular UI. Use semantic colors and normal SDR text contrast for readability; reserve custom HDR rendering for niche visual effects where you accept the complexity and readability tradeoffs.
WCAG Contrast for Text
Section titled “WCAG Contrast for Text”| Level | Normal Text | Large Text (18pt+ or 14pt+ bold) |
|---|---|---|
| AA | 4.5:1 | 3:1 |
| AAA | 7:1 | 4.5:1 |
Apple’s semantic label colors meet WCAG AA on their corresponding backgrounds:
.labelon.systemBackground→ 21:1 (light), 18:1 (dark).secondaryLabelon.systemBackground→ meets AA.tertiaryLabel→ may NOT meet AA for small text (use for decorative/hint only)
Common Pitfalls
Section titled “Common Pitfalls”- Attributed string default foreground is black, not
.label— invisible in dark mode. Always set explicitly. - Using
UIColor.black/.whiteinstead of.label— doesn’t adapt to dark mode. - Using
.lightText/.darkText— legacy, non-adaptive. Use.labelvariants. - macOS: Using
.labelColorfor NSTextView body text — Use.textColor(fully opaque, no vibrancy). - P3 colors for body text — Saturated colors reduce readability. Use for accents only.
- Not testing high contrast mode — Some custom colors fail WCAG AA when Increase Contrast is on.
- CALayer colors (CGColor) not updating — Must manually re-resolve on trait changes.
Documentation Scope
Section titled “Documentation Scope”This page documents the apple-text-colors reference skill. Use it when the subsystem is already known and you need mechanics, behavior, or API detail.
Related
Section titled “Related”apple-text-formatting-ref: Use when the user already knows the formatting problem and needs exact text-formatting attributes such as NSAttributedString.Key values, underline styles, shadows, lists, tables, or view-compatibility rules. Reach for this when the job is verifying concrete formatting APIs, not choosing the text model.apple-text-dynamic-type: Use when implementing Dynamic Type for text views, scaling custom fonts, handling accessibility sizes, responding to content size category changes, or diagnosing what breaks at large sizes. Reach for this when the problem is text scaling and accessibility sizing, not VoiceOver or general accessibility.apple-text-attributed-string: Use when choosing between AttributedString and NSAttributedString, defining custom attributes, converting between them, or deciding which model should own rich text in a feature. Reach for this when the main task is the attributed-string model decision, not low-level formatting catalog lookup.
Full SKILL.md source
---name: apple-text-colorsdescription: Use when choosing text colors, semantic colors, dark-mode behavior, wide-color or HDR text, or making attributed-text colors adapt correctly across UIKit, AppKit, and SwiftUI. Reach for this when the problem is specifically text color behavior, not broader formatting or typography.license: MIT---
# Text Colors, Dark Mode & Wide Color
Use this skill when the main question is how text color should adapt across UIKit, AppKit, SwiftUI, dark mode, or wide-color environments.
## When to Use
- You need semantic text colors.- You are debugging dark-mode text issues.- You need to know whether wide color or HDR applies to text rendering.
## Quick Decision
- Need adaptive body text colors -> use semantic label/text colors- Need attributed text colors that survive mode changes -> resolve dynamic colors correctly- Need advanced formatting attributes -> `/skill apple-text-formatting-ref`
## Core Guidance
## UIKit Semantic Text Colors (Complete)
### Label Hierarchy
| Color | Light Mode | Dark Mode | Use For ||-------|-----------|-----------|---------|| `.label` | Black (a1.0) | White (a1.0) | Primary text || `.secondaryLabel` | #3C3C43 (a0.6) | #EBEBF5 (a0.6) | Secondary text || `.tertiaryLabel` | #3C3C43 (a0.3) | #EBEBF5 (a0.3) | Tertiary text || `.quaternaryLabel` | #3C3C43 (a0.18) | #EBEBF5 (a0.18) | Disabled/hint text || `.placeholderText` | #3C3C43 (a0.3) | #EBEBF5 (a0.3) | Placeholder in fields || `.link` | #007AFF | #0984FF | Tappable links |
### Legacy (NON-Dynamic — Avoid)
| Color | Value | Problem ||-------|-------|---------|| `.lightText` | White (a0.6) — always | Does NOT adapt to dark mode || `.darkText` | Black (a1.0) — always | Does NOT adapt to dark mode |
**Always use `.label` instead of `.darkText`, `.lightText`, or hardcoded black/white.**
### System Tint Colors (Adaptive)
All shift slightly between light and dark for contrast:
| Color | Light | Dark ||-------|-------|------|| `.systemBlue` | #007AFF | #0A84FF || `.systemRed` | #FF3B30 | #FF453A || `.systemGreen` | #34C759 | #30D158 || `.systemOrange` | #FF9500 | #FF9F0A || `.systemPurple` | #AF52DE | #BF5AF2 || `.systemPink` | #FF2D55 | #FF375F || `.systemYellow` | #FFCC00 | #FFD60A || `.systemIndigo` | #5856D6 | #5E5CE6 || `.systemTeal` | #5AC8FA | #64D2FF || `.systemCyan` | #32ADE6 | #64D2FF || `.systemMint` | #00C7BE | #63E6E2 || `.systemBrown` | #A2845E | #AC8E68 |
## AppKit Semantic Text Colors
### Critical: `.textColor` vs `.labelColor`
| Color | Alpha | Vibrancy | Use For ||-------|-------|----------|---------|| `.textColor` | 1.0 (fully opaque) | ❌ No | Document body text, NSTextView content || `.labelColor` | ~0.85 | ✅ Yes | UI labels, buttons, sidebar items |
**On macOS, `.textColor` is the right default for text views.** `.labelColor` is for UI chrome. They look similar but behave differently with vibrancy.
### Full macOS Text Color Catalog
| Color | Light | Dark | Use For ||-------|-------|------|---------|| `.textColor` | Black (a1.0) | White (a1.0) | Body text || `.textBackgroundColor` | White (a1.0) | #1E1E1E (a1.0) | Text view background || `.selectedTextColor` | White | White | Selected text foreground || `.selectedTextBackgroundColor` | #0063E1 | #0050AA | Selection highlight || `.unemphasizedSelectedTextColor` | Black | White | Selection (window inactive) || `.unemphasizedSelectedTextBackgroundColor` | #DCDCDC | #464646 | Selection bg (inactive) || `.placeholderTextColor` | Black (a0.25) | White (a0.25) | Placeholder text || `.headerTextColor` | Black (a0.85) | White (a1.0) | Section headers || `.linkColor` | #0068DA | #419CFF | Hyperlinks || `.controlTextColor` | Black (a0.85) | White (a0.85) | Control labels || `.disabledControlTextColor` | Black (a0.25) | White (a0.25) | Disabled controls |
## SwiftUI Semantic Colors
### Text Foreground Styles
```swiftText("Primary").foregroundStyle(.primary) // ≈ .labelText("Secondary").foregroundStyle(.secondary) // ≈ .secondaryLabelText("Tertiary").foregroundStyle(.tertiary) // ≈ .tertiaryLabel```
### Bridging UIKit/AppKit Colors
```swift// Use UIKit semantic colors in SwiftUIText("Label").foregroundStyle(Color(uiColor: .label))Text("Link").foregroundStyle(Color(uiColor: .link))
// macOSText("Text").foregroundStyle(Color(nsColor: .textColor))```
### SwiftUI Color Literals
```swiftColor.primary // Adaptive: black in light, white in darkColor.secondary // Adaptive: with reduced opacityColor.accentColor // App tint color (default: .blue)```
## Dark Mode Adaptation
### What Auto-Adapts
| Scenario | Auto-adapts? ||----------|-------------|| UILabel with `.textColor = .label` | ✅ || UITextView with no explicit color | ✅ (defaults to `.label` in TextKit 2) || NSAttributedString with `.foregroundColor: UIColor.label` | ✅ UIKit re-resolves at draw time || NSAttributedString with `.foregroundColor: UIColor.red` (hardcoded) | ❌ Stays red always || NSAttributedString with `.foregroundColor: UIColor.systemRed` | ✅ Shifts between light/dark variants || SwiftUI Text with `.foregroundStyle(.primary)` | ✅ || SwiftUI Text with `.foregroundStyle(Color.red)` | ✅ (Color.red is adaptive) || CALayer.borderColor (CGColor) | ❌ Must update manually |
### The Default Foreground Color Trap
**UIKit default attributed string foreground is black, NOT `.label`.**
```swift// ❌ This text will be invisible in dark modelet str = NSAttributedString(string: "Hello") // foreground defaults to .blacktextView.attributedText = str
// ✅ Always set foreground color explicitlylet str = NSAttributedString(string: "Hello", attributes: [ .foregroundColor: UIColor.label])```
### Making Attributed String Colors Dynamic
Use semantic UIColors — they auto-resolve:
```swiftlet attrs: [NSAttributedString.Key: Any] = [ .foregroundColor: UIColor.label, // ✅ Adapts .backgroundColor: UIColor.systemYellow, // ✅ Adapts]```
**Do NOT use:**```swift.foregroundColor: UIColor(red: 0, green: 0, blue: 0, alpha: 1) // ❌ Always black.foregroundColor: UIColor.black // ❌ Always black```
### Custom Dynamic Colors
```swiftlet adaptiveColor = UIColor { traitCollection in switch traitCollection.userInterfaceStyle { case .dark: return UIColor(red: 0.9, green: 0.9, blue: 1.0, alpha: 1.0) default: return UIColor(red: 0.1, green: 0.1, blue: 0.2, alpha: 1.0) }}```
### High Contrast (Increase Contrast)
Semantic colors also adapt to the Increase Contrast accessibility setting:
```swiftlet color = UIColor { traitCollection in if traitCollection.accessibilityContrast == .high { return .black // Higher contrast variant } else { return UIColor(white: 0.3, alpha: 1.0) }}```
Apple's built-in semantic colors already handle this — `.label` becomes pure black/white in high contrast.
### Responding to Trait Changes
```swift// iOS 17+ (preferred)registerForTraitChanges([UITraitUserInterfaceStyle.self]) { (self: Self, _) in self.updateColors()}
// iOS 13-16override func traitCollectionDidChange(_ previous: UITraitCollection?) { super.traitCollectionDidChange(previous) if traitCollection.hasDifferentColorAppearance(comparedTo: previous) { updateColors() }}```
## Wide Color / Display P3
### Creating P3 Colors for Text
```swift// UIKitlet p3Color = UIColor(displayP3Red: 1.0, green: 0.1, blue: 0.1, alpha: 1.0)
// AppKitlet p3Color = NSColor(displayP3Red: 1.0, green: 0.1, blue: 0.1, alpha: 1.0)
// SwiftUIlet p3Color = Color(.displayP3, red: 1.0, green: 0.1, blue: 0.1, opacity: 1.0)
// Note: SwiftUI Color(red:green:blue:) defaults to sRGB, NOT P3```
### Does Text Actually Render in P3?
**Yes**, on P3 displays. Text rendering goes through Core Text → Core Graphics, which renders in the color space of the CGContext. On P3 displays (iPhone 7+, iPad Pro, Mac with P3 display), the backing CALayer uses a P3 color space, so text colors outside sRGB gamut are rendered correctly.
**Practical impact for text:** Minimal. Text readability depends on contrast, not gamut. P3 colors that are more saturated than sRGB may reduce readability. Use P3 for branding colors on text, not for body copy.
## HDR / EDR for Text
### Can Text Be HDR?
**Not through the standard text APIs in a normal, supported way.** Standard text views (UILabel, UITextView, NSTextView, SwiftUI Text) are not the usual HDR rendering path and should be treated as SDR text for UI design.
Apple's EDR guidance treats 1.0 as reference/UI white, and most UI should not exceed that. Pushing text above it tends to create a glowing look that hurts readability.
### What About `allowedDynamicRange(.high)`?
SwiftUI does expose `allowedDynamicRange` as a view environment using `Image.DynamicRange`, but Apple's HDR APIs and examples are centered on image/video/custom rendering content, not standard text as a recommended HDR workflow.
### Possible Workarounds
1. Custom Metal/CAMetalLayer rendering using public Core Animation and Metal APIs2. Private CAFilter APIs (not App Store safe)3. Core Image or other custom compositing pipelines
The first category can be App Store-safe if it stays on documented APIs, but it is custom graphics work, not standard text rendering. Treat it as a special effect path, not normal body text/UI text design.
**Bottom line:** HDR text is not a standard or recommended text treatment for regular UI. Use semantic colors and normal SDR text contrast for readability; reserve custom HDR rendering for niche visual effects where you accept the complexity and readability tradeoffs.
## WCAG Contrast for Text
| Level | Normal Text | Large Text (18pt+ or 14pt+ bold) ||-------|-------------|----------------------------------|| AA | 4.5:1 | 3:1 || AAA | 7:1 | 4.5:1 |
Apple's semantic label colors meet WCAG AA on their corresponding backgrounds:- `.label` on `.systemBackground` → 21:1 (light), 18:1 (dark)- `.secondaryLabel` on `.systemBackground` → meets AA- `.tertiaryLabel` → may NOT meet AA for small text (use for decorative/hint only)
## Common Pitfalls
1. **Attributed string default foreground is black, not `.label`** — invisible in dark mode. Always set explicitly.2. **Using `UIColor.black`/`.white` instead of `.label`** — doesn't adapt to dark mode.3. **Using `.lightText`/`.darkText`** — legacy, non-adaptive. Use `.label` variants.4. **macOS: Using `.labelColor` for NSTextView body text** — Use `.textColor` (fully opaque, no vibrancy).5. **P3 colors for body text** — Saturated colors reduce readability. Use for accents only.6. **Not testing high contrast mode** — Some custom colors fail WCAG AA when Increase Contrast is on.7. **CALayer colors (CGColor) not updating** — Must manually re-resolve on trait changes.
## Related Skills
- Use `/skill apple-text-formatting-ref` for broader attributed-text formatting rules.- Use `/skill apple-text-dynamic-type` when color decisions interact with accessibility text sizing.- Use `/skill apple-text-attributed-string` when the color question is really about attribute storage and conversion.