Skip to content

Theming

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

Theming

DesignFoundation's theming system is a structured token hierarchy injected into the SwiftUI environment. Every component in the library reads from a single DFTheme value cascaded down the view tree — there is no global mutable state, no singletons, and no appearance proxy to fight with. You apply a theme once at a scene root, and everything below it inherits automatically.

Related pages: Getting-Started · Cross-Platform · Changelog


How Theming Works

DFTheme is a plain Sendable struct that bundles every design decision for a UI into one value:

public struct DFTheme: Sendable {
    public var colors:     DFColorTokens
    public var typography: DFTypographyTokens
    public var spacing:    DFSpacingTokens
    public var radius:     DFRadiusTokens
    public var shadows:    DFShadowTokens
    public var animation:  DFAnimationTokens
    public var components: DFComponentTokens
}

It is stored in a SwiftUI environment key (\.dfTheme) and propagates to all child views exactly like \.font or \.colorScheme. Any sub-tree can override the theme for its own descendants without affecting the rest of the hierarchy.

All DF components read \.dfTheme internally. You never pass a theme directly to a component — you set it on a parent view and every component beneath it picks it up.


Applying a Theme

Using a Preset (Recommended)

@main
struct MyApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
                .dfThemePreset(.slate)
        }
    }
}

Available presets: .slate · .aurora · .copper · .sage

Using a Specific DFTheme Directly

ContentView()
    .dfTheme(.default)

ContentView()
    .dfTheme(.auroraLight)

ContentView()
    .dfTheme(.copperDark)

Overriding a Theme for a Sub-tree

VStack {
    StandardCard()          // inherits the root theme

    PromoSection()
        .dfTheme(.copperLight)  // this subtree uses Copper Light only
}

Color Tokens

DFColorTokens holds every semantic color used across the system.

Token Purpose Default (iOS)
primary Brand accent; buttons, links, highlights #007AFF (system blue)
secondary Supporting brand color; secondary actions Color(white: 0.55)
accent Complementary accent; badges, callouts #007AFF
background App canvas — the outermost background UIColor.systemBackground
surface Grouped panels, secondary regions UIColor.secondarySystemBackground
surfaceElevated Cards, raised panels, sheet backgrounds UIColor.tertiarySystemBackground
textPrimary Primary reading text, titles UIColor.label
textSecondary Supporting text, captions, metadata UIColor.secondaryLabel
textDisabled Inactive labels, placeholder text UIColor.tertiaryLabel
border Dividers, field outlines, separators UIColor.separator
interactiveFill Default fill for interactive controls #007AFF
interactiveHover Hover state for interactive controls #007AFF at 85%
interactivePressed Pressed state for interactive controls #007AFF at 70%
interactiveDisabled Disabled state fill UIColor.systemGray5
destructive Destructive actions, error states UIColor.systemRed
success Confirmations, positive feedback UIColor.systemGreen
warning Warnings, caution states UIColor.systemOrange
info Informational callouts UIColor.systemBlue
respectsColorScheme When true, system colors adapt to dark/light true

Accessing Color Tokens

@Environment(\.dfTheme) private var theme

var body: some View {
    Text("Hello")
        .foregroundStyle(theme.colors.textPrimary)
        .padding(theme.spacing.md)
        .background(theme.colors.surface)
        .cornerRadius(theme.radius.md)
}

Spacing Tokens

Token Value (pt) Typical Use
xs 4 Icon-to-label gap, tight internal padding
sm 8 Between related elements, compact padding
md 12 Standard internal padding for form controls
lg 16 Standard card/panel padding, button horizontal padding
xl 24 Between sections, generous card padding
xxl 32 Between major layout regions, hero section padding

Radius Tokens

Token Default Value (pt) Typical Use
none 0 Sharp-cornered elements
sm 4 Tags, inline chips, tooltip backgrounds
md 8 Buttons, text fields, list cells, standard cards
lg 12 Prominent cards, modal sheets, sidebar panels
full 9999 Pill shapes, circular avatars, toggle tracks

Preset Radius Scales

Preset sm md lg Character
Default 4 8 12 Balanced, neutral
Slate 4 8 12 Restrained
Aurora 4 10 16 Slightly softer
Copper 3 6 10 Tight and grounded
Sage 6 12 18 Generous and relaxed

Typography Tokens

DFTypographyTokens maps semantic roles to DFTextStyle values — each containing a SwiftUI Font, a lineSpacing, and a tracking value.

Token SwiftUI Font Base Weight Role
display .largeTitle .bold Full-screen heroes, onboarding headlines
title .title2 .semibold Screen-level focal metrics, modal headers
headline .headline .semibold Section headers, emphasized card content
body .body (default) Primary reading text, form field input
label .subheadline .medium Dense row primary text, toolbar labels
caption .caption (default) Metadata, timestamps, badges

Using Typography Tokens

// Via DFText (recommended)
DFText("Dashboard", style: .title)
DFText("Last updated 3 minutes ago", style: .caption)

// In custom views
Text("Section Header")
    .font(theme.typography.headline.font)
    .lineSpacing(theme.typography.headline.lineSpacing)
    .tracking(theme.typography.headline.tracking)
    .foregroundStyle(theme.colors.textPrimary)

Animation Tokens

Token Duration Curve Typical Use
fast 0.15 s easeInOut Hover states, toggle fills, button presses
default 0.25 s easeInOut Sheet presentation, card expansion
slow 0.40 s easeInOut Page transitions, onboarding steps

Shadow Tokens

Token Default radius Default y offset Default opacity
none 0 0
sm 4 2 8%
md 8 4 12%
lg 16 8 18%
RoundedRectangle(cornerRadius: theme.radius.lg)
    .fill(theme.colors.surfaceElevated)
    .shadow(
        color: theme.shadows.md.color,
        radius: theme.shadows.md.radius,
        x: theme.shadows.md.x,
        y: theme.shadows.md.y
    )

Component Tokens

DFComponentTokens holds per-component overrides for sizing, padding, and typography. Every property is optional (CGFloat?). A nil value means "inherit from the global token."

Component Overridable properties
button cornerRadius, horizontalPadding, verticalPadding, labelStyle
textField cornerRadius, horizontalPadding, verticalPadding, inputStyle, labelStyle
card cornerRadius, padding
avatar defaultSize, borderWidth
badge cornerRadius, horizontalPadding, verticalPadding
icon defaultSize
let toolbarComponents = DFComponentTokens(
    button: DFButtonTokens(
        cornerRadius:      6,
        horizontalPadding: 10,
        verticalPadding:   6
    )
)

ToolbarView()
    .dfTheme(DFTheme(components: toolbarComponents))

Built-in Theme Presets

Default

Uses platform semantic colors and the standard token scale. Adapts to light and dark mode via UIKit/AppKit adaptive colors.

Personality: Invisible — looks right on every platform out of the box.

Slate

A cool blue-grey palette built for professional and productivity interfaces.

Light primary: #1C3D5A (deep navy) · Dark primary: #63B5F6 (sky blue)

ContentView().dfThemePreset(.slate)

ContentView().dfTheme(.slateLight)   // always light
ContentView().dfTheme(.slateDark)    // always dark

Aurora

A vibrant violet preset with soft shadows and generously rounded corners.

Light primary: #6C47FF · Dark primary: #A78BFA (soft lavender)

ContentView().dfThemePreset(.aurora)

Copper

A warm earth-tone palette with tight corner radii and the strongest shadow scale.

Light primary: #C4623E (terracotta) · Dark primary: #F4A261 (warm amber)

ContentView().dfThemePreset(.copper)

Sage

A natural forest-green palette with the most generous corner radii and the most subtle shadows.

Light primary: #2D6A4F (forest green) · Dark primary: #74C69D (mint green)

ContentView().dfThemePreset(.sage)

Preset Summary

Preset Radius md Shadow sm opacity Primary (Light) Primary (Dark)
Default 8 8% System blue System blue
Slate 8 8% Deep navy Sky blue
Aurora 10 6% Vibrant violet Soft lavender
Copper 6 12% Terracotta Warm amber
Sage 12 4% Forest green Mint green

Custom Themes

Minimal Override (Just Colors)

let midnightTheme = DFTheme(
    colors: DFColorTokens(
        primary:         Color(red: 0.18, green: 0.47, blue: 0.98),
        accent:          Color(red: 0.35, green: 0.62, blue: 1.0),
        background:      Color(red: 0.05, green: 0.06, blue: 0.12),
        surface:         Color(red: 0.09, green: 0.11, blue: 0.20),
        surfaceElevated: Color(red: 0.13, green: 0.16, blue: 0.28),
        textPrimary:     Color(red: 0.92, green: 0.94, blue: 1.0),
        textSecondary:   Color(red: 0.55, green: 0.62, blue: 0.78),
        textDisabled:    Color(red: 0.28, green: 0.33, blue: 0.48),
        border:          Color(red: 0.16, green: 0.20, blue: 0.34),
        interactiveFill:     Color(red: 0.18, green: 0.47, blue: 0.98),
        interactiveHover:    Color(red: 0.18, green: 0.47, blue: 0.98).opacity(0.85),
        interactivePressed:  Color(red: 0.18, green: 0.47, blue: 0.98).opacity(0.70),
        interactiveDisabled: Color(red: 0.12, green: 0.16, blue: 0.27),
        destructive: Color(red: 0.94, green: 0.32, blue: 0.31),
        success:     Color(red: 0.40, green: 0.73, blue: 0.42),
        warning:     Color(red: 1.0,  green: 0.66, blue: 0.15),
        info:        Color(red: 0.40, green: 0.72, blue: 0.97),
        respectsColorScheme: false
    )
)

ContentView()
    .dfTheme(midnightTheme)

Building a Light/Dark Pair as a DFThemePreset

let myPreset = DFThemePreset(
    light: myLightTheme,
    dark:  myDarkTheme
)

ContentView()
    .dfThemePreset(myPreset)

Dark Mode and Color Scheme Adaptation

DesignFoundation supports two distinct adaptation strategies, controlled per theme by DFColorTokens.respectsColorScheme.

Strategy 1 — System Adaptive (Default Theme): When respectsColorScheme = true, color tokens are backed by platform semantic colors that automatically shift between light and dark values.

Strategy 2 — Explicit Light/Dark Pair (Presets): When respectsColorScheme = false, the DFThemePreset mechanism holds two separate DFTheme values and resolves the correct one via .dfThemePreset(_:).

Do not combine both strategies in one preset — set the same respectsColorScheme value across both light and dark variants.


If you need production-ready auth screens, full vertical layouts, or shell layouts already matched to your theme, DesignFoundation Pro ships these ready to drop in. See https://nerdsnipe-inc.github.io/design-foundation/pro/ for details.

Clone this wiki locally