Skip to content

Theme Presets

Daniel S edited this page Jul 2, 2026 · 1 revision

Theme Presets

Every DesignFoundation theme is a light/dark pair — a DFThemePreset bundles two DFTheme instances and resolves the correct variant at runtime by reading the system color scheme (@Environment(\.colorScheme)). You never branch on colorScheme yourself; you apply a preset and the framework handles the rest.

Presets encode more than color. Each one ships with a calibrated radius scale, shadow opacity, and typographic character that work together to create a coherent aesthetic. Swapping .slate for .copper changes how the entire app feels, not just its palette.

Related pages: Theming · Style-System · Swift-6-Concurrency


Contents

  1. Applying Presets
  2. Default
  3. Slate
  4. Aurora
  5. Copper
  6. Sage
  7. Comparison Table
  8. Choosing a Preset
  9. Building a Custom Preset
  10. Previewing in Xcode Canvas
  11. Runtime Preset Switching
  12. Per-Platform Preset Overrides

Applying Presets

Auto-resolving modifier

// Resolves light or dark variant based on the system color scheme
ContentView()
    .dfThemePreset(.slate)

Apply this modifier at the scene root — typically on the view returned by your WindowGroup body. All DesignFoundation components in the subtree inherit the resolved theme automatically via @Environment(\.dfTheme).

Forcing a specific variant

// Always light — ignores system setting
ContentView().dfTheme(.slateLight)

// Always dark — ignores system setting
ContentView().dfTheme(.slateDark)

Each preset exposes named DFTheme constants following the <preset><Variant> pattern: .defaultLight / .defaultDark, .slateLight / .slateDark, .auroraLight / .auroraDark, .copperLight / .copperDark, .sageLight / .sageDark.

Sub-tree override

A preset applied to any view overrides the parent theme for that subtree only. The rest of the app is unaffected.

VStack {
    MainContent()          // inherits app-level .slate

    PromoSection()
        .dfThemePreset(.copper)   // warm earth tones for this block only
}

Runtime switching

@State private var preset: DFThemePreset = .slate

var body: some View {
    ContentView()
        .dfThemePreset(preset)
}

Default Preset

Personality. Default delegates to SwiftUI's semantic color system — Color.primary, Color.accentColor, Color.background. It carries no opinionated hue of its own, blending into any context and respecting the user's system accent color.

Token Value
Primary (light) System accent
Primary (dark) System accent
radius.md 8 pt
Shadow opacity 8%

Ideal for: utilities, productivity tools, developer tools, apps that must match the OS exactly, prototypes before a brand palette is decided.

ContentView().dfThemePreset(.default)

Slate Preset

Personality. A cool, authoritative presence rooted in deep navy and clear sky blue. In light mode, the deep navy primary communicates trust and expertise. In dark mode, sky blue lifts the palette into something crisp and legible. The character is measured, structured, and confident.

Token Value
Primary (light) #1C3D5A — deep navy
Primary (dark) #63B5F6 — sky blue
radius.md 8 pt
Shadow opacity 8%

Ideal for: finance, B2B SaaS, analytics dashboards, enterprise productivity.

struct PortfolioCard: View {
    let symbol: String
    let change: Double

    var body: some View {
        DFCard {
            HStack {
                VStack(alignment: .leading, spacing: 4) {
                    DFText(symbol, style: .headline)
                    DFText("Today's change", style: .caption)
                }
                Spacer()
                DFBadge(
                    text: change >= 0 ? "+\(change)%" : "\(change)%",
                    color: change >= 0 ? .green : .red
                )
            }
        }
        .dfThemePreset(.slate)
    }
}

Aurora Preset

Personality. Bold violet and soft lavender — a palette built for imagination. In light mode, saturated violet pulses with creative energy. In dark mode, soft lavender is dreamy and ethereal. The radius scale is more generous than Default and Slate, rounding corners into something fluid and inviting. The lightest shadow depth of all presets lets surfaces float.

Token Value
Primary (light) #6C47FF — bold violet
Primary (dark) #A78BFA — soft lavender
radius.md 10 pt
Shadow opacity 6%

Ideal for: creative tools, AI-powered apps, social platforms, journaling, education.

struct IdeaCard: View {
    @State private var query = ""

    var body: some View {
        DFCard {
            VStack(spacing: 16) {
                DFText("Your Creative Space", style: .headline)
                DFTextField("Search ideas…", text: $query, leadingIcon: "sparkles")
                DFButton("New Idea", icon: "plus") { addIdea() }
            }
        }
        .dfThemePreset(.aurora)
    }
}

Copper Preset

Personality. Terracotta and warm amber — tactile and warm. Light mode recalls handmade ceramics and sun-dried clay. Dark mode glows like embers. The tightest radius scale gives Copper a structured, handcrafted look. The strongest shadow opacity adds tangible depth to every surface.

Token Value
Primary (light) #C4623E — terracotta
Primary (dark) #F4A261 — warm amber
radius.md 6 pt
Shadow opacity 12%

Ideal for: food and recipe apps, hospitality, lifestyle e-commerce, wellness, travel.

struct RecipeCard: View {
    let recipe: Recipe

    var body: some View {
        DFCard {
            VStack(alignment: .leading, spacing: 8) {
                DFText(recipe.name, style: .headline)
                HStack {
                    DFIcon("clock", size: .sm)
                    DFText("\(recipe.minutes) min", style: .caption)
                    DFBadge(text: recipe.difficulty)
                }
                DFButton("View Recipe") { openRecipe() }
            }
        }
        .dfThemePreset(.copper)
    }
}

Sage Preset

Personality. Forest green grounds the light palette in something ancient and living; mint green in dark mode refreshes without demanding attention. The most generous radius scale curves every surface into something rounded and unhurried. The near-imperceptible shadow creates an interface that feels like ambient light, not a spotlight. Sage communicates calm, health, and intentionality.

Token Value
Primary (light) #2D6A4F — forest green
Primary (dark) #74C69D — mint green
radius.md 12 pt
Shadow opacity 4%

Ideal for: health and fitness, meditation, sustainability, personal finance, nature apps.

struct WellnessCheckIn: View {
    @State private var mood: Double = 0.7
    @State private var notes = ""

    var body: some View {
        DFCard {
            VStack(alignment: .leading, spacing: 16) {
                DFText("How are you feeling?", style: .headline)
                DFSlider(value: $mood, in: 0...1, label: "Mood")
                DFTextField("Any notes for today…", text: $notes, leadingIcon: "leaf")
                DFButton("Save check-in") { saveCheckIn() }
            }
        }
        .dfThemePreset(.sage)
    }
}

Comparison Table

Preset Primary (Light) Primary (Dark) radius.md Shadow Character
Default System accent System accent 8 pt 8% Neutral, platform-native
Slate #1C3D5A navy #63B5F6 sky blue 8 pt 8% Professional, authoritative
Aurora #6C47FF violet #A78BFA lavender 10 pt 6% Creative, luminous, airy
Copper #C4623E terracotta #F4A261 amber 6 pt 12% Warm, tactile, grounded
Sage #2D6A4F forest #74C69D mint 12 pt 4% Calm, natural, open

Choosing a Preset

App domain Recommended preset Rationale
Finance / banking .slate Trust, authority, analytical precision
Developer tools .slate or .default Structured, doesn't distract from content
Creative / AI apps .aurora Bold, imaginative; signals innovation
Social / community .aurora Inviting, modern, emotionally warm
Food / hospitality .copper Earthy warmth matches culinary context
E-commerce (lifestyle) .copper Artisanal quality feel
Health / fitness .sage Calm, trustworthy; green carries wellness
Meditation / mindfulness .sage Minimal shadow and open radius reinforce tranquility
Utilities .default Matches system expectations
Education .aurora or .sage Aurora for engagement; Sage for focus

Tip: choose by emotional register: trustworthy → .slate, expressive → .aurora, warm → .copper, calm → .sage, native → .default.


Building a Custom Preset

import DesignFoundation

let midnightLight = DFTheme(
    colors: DFColorTokens(
        primary:         Color(hex: "#2A3F8F"),
        background:      Color(hex: "#F5F6FA"),
        surface:         Color(hex: "#FFFFFF"),
        surfaceElevated: Color(hex: "#ECEEF7"),
        textPrimary:     Color(hex: "#111827"),
        textSecondary:   Color(hex: "#6B7280"),
        border:          Color(hex: "#D1D5EB"),
        accent:          Color(hex: "#5B6FD4"),
        success:         Color(hex: "#16A34A"),
        warning:         Color(hex: "#D97706"),
        error:           Color(hex: "#DC2626")
    ),
    radius: DFRadiusTokens(sm: 4, md: 9, lg: 16, full: 999),
    shadow: DFShadowTokens(opacity: 0.08)
)

let midnightDark = DFTheme(
    colors: DFColorTokens(
        primary:         Color(hex: "#7B93E8"),
        background:      Color(hex: "#0D1023"),
        surface:         Color(hex: "#151929"),
        surfaceElevated: Color(hex: "#1E2440"),
        textPrimary:     Color(hex: "#F1F3FF"),
        textSecondary:   Color(hex: "#9EA8CC"),
        border:          Color(hex: "#2C3560"),
        accent:          Color(hex: "#A0AEFF"),
        success:         Color(hex: "#4ADE80"),
        warning:         Color(hex: "#FCD34D"),
        error:           Color(hex: "#F87171")
    ),
    radius: DFRadiusTokens(sm: 4, md: 9, lg: 16, full: 999),
    shadow: DFShadowTokens(opacity: 0.12)
)

let midnightPreset = DFThemePreset(light: midnightLight, dark: midnightDark)

// Apply like any built-in preset
ContentView().dfThemePreset(midnightPreset)

Tip: Extend DFThemePreset with a static property to get dot-shorthand:

extension DFThemePreset {
    static let midnight = DFThemePreset(light: midnightLight, dark: midnightDark)
}

ContentView().dfThemePreset(.midnight)

Previewing in Xcode Canvas

DFThemePreset conforms to CaseIterable, so you can render all five built-in presets in a single #Preview block:

#Preview("All Presets") {
    ScrollView {
        VStack(spacing: 24) {
            ForEach(DFThemePreset.allCases) { preset in
                VStack(alignment: .leading, spacing: 8) {
                    DFText(preset.name, style: .caption)
                        .padding(.horizontal)
                    MyCardComponent()
                        .dfThemePreset(preset)
                }
            }
        }
        .padding(.vertical)
    }
}

For light/dark comparison of a single preset:

#Preview("Slate — Light and Dark") {
    HStack(spacing: 0) {
        MyCardComponent()
            .dfTheme(.slateLight)
            .frame(maxWidth: .infinity)
        MyCardComponent()
            .dfTheme(.slateDark)
            .frame(maxWidth: .infinity)
            .preferredColorScheme(.dark)
    }
}

Runtime Preset Switching

import DesignFoundation
import Observation

@Observable
final class AppSettings {
    var themePreset: DFThemePreset = .slate

    private let key = "app.themePreset"

    init() {
        if let saved = UserDefaults.standard.string(forKey: key),
           let preset = DFThemePreset.allCases.first(where: { $0.name == saved }) {
            themePreset = preset
        }
    }

    func save() {
        UserDefaults.standard.set(themePreset.name, forKey: key)
    }
}

@main
struct MyApp: App {
    @State private var settings = AppSettings()

    var body: some Scene {
        WindowGroup {
            ContentView()
                .environment(settings)
                .dfThemePreset(settings.themePreset)
        }
    }
}

struct AppearanceSettingsView: View {
    @Environment(AppSettings.self) private var settings

    var body: some View {
        DFCard {
            VStack(alignment: .leading, spacing: 16) {
                DFText("Appearance", style: .headline)

                DFPicker(
                    "Theme",
                    selection: Bindable(settings).themePreset,
                    options: DFThemePreset.allCases
                )

                // Live preview of the selected theme
                PreviewTile()
                    .dfThemePreset(settings.themePreset)

                DFButton("Save") { settings.save() }
            }
        }
        .padding()
    }
}

Per-Platform Preset Overrides

@main
struct MyApp: App {
    var body: some Scene {
        WindowGroup {
            #if os(macOS)
            ContentView().dfThemePreset(.slate)   // professional on desktop
            #else
            ContentView().dfThemePreset(.aurora)  // warmer on mobile
            #endif
        }
    }
}

visionOS note: .aurora is particularly well-suited to visionOS — its light shadows and generous radius feel at home in a spatial context. .sage and .default also translate well. .copper's strong shadows can feel heavy in mixed reality.


See also: Theming · Style-System · Swift-6-Concurrency · Liquid-Glass

Clone this wiki locally