-
Notifications
You must be signed in to change notification settings - Fork 0
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
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.
@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
ContentView()
.dfThemePreset(.slate)
}
}
}Available presets: .slate · .aurora · .copper · .sage
ContentView()
.dfTheme(.default)
ContentView()
.dfTheme(.auroraLight)
ContentView()
.dfTheme(.copperDark)VStack {
StandardCard() // inherits the root theme
PromoSection()
.dfTheme(.copperLight) // this subtree uses Copper Light only
}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 |
@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)
}| 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 |
| 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 | 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 |
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 |
// 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)| 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 |
| 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
)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))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.
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 darkA vibrant violet preset with soft shadows and generously rounded corners.
Light primary: #6C47FF · Dark primary: #A78BFA (soft lavender)
ContentView().dfThemePreset(.aurora)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)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 | 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 |
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)let myPreset = DFThemePreset(
light: myLightTheme,
dark: myDarkTheme
)
ContentView()
.dfThemePreset(myPreset)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.