Skip to main content
  1. Projects/

DesignTokens

A Swift package providing a themeable design token system for iOS apps. Defines a Theme protocol with semantic colors, typography, and spacing tokens that integrate with SwiftUI via @Environment. Ships with sensible defaults mapped to iOS system colors, and includes WCAG contrast checking utilities.

Requires iOS 26+ and Swift 6.2.

Checkout on GitHub

Setup #

// 1. Add the package dependency
// 2. Import in your files
import DesignTokens

// 3. Inject at app root
ContentView()
    .theme(DefaultTheme())

// 4. Read in any view
@Environment(\.theme) private var theme

Colors #

TokenPurposeMaps to
theme.primaryBrand / tint color.accentColor
theme.secondarySupporting brand color.secondaryLabel
theme.accentInteractive elements.accentColor
theme.backgroundMain screen background.systemBackground
theme.backgroundSecondaryGrouped/inset background.secondarySystemBackground
theme.backgroundTertiaryNested grouped background.tertiarySystemBackground
theme.surfaceCard / grouped container.systemGroupedBackground
theme.surfaceSecondaryNested card.secondarySystemGroupedBackground
theme.labelPrimary text.label
theme.labelSecondarySupporting text.secondaryLabel
theme.labelTertiaryPlaceholder / disabled.tertiaryLabel
theme.labelQuaternaryFaintest text.quaternaryLabel
theme.separatorDividers.separator
theme.destructiveDelete / error.red
theme.successConfirmation / done.green
theme.warningCaution states.orange
theme.infoInformational highlights.blue

Typography #

All tokens map to Font.TextStyle so Dynamic Type works automatically.

TokenText StyleTypical Use
theme.typography.largeTitle.largeTitleNav bar large title
theme.typography.title.titleSection header
theme.typography.title2.title2Subsection header
theme.typography.title3.title3Card header
theme.typography.headline.headlineEmphasized label
theme.typography.body.bodyDefault reading text
theme.typography.callout.calloutSecondary content
theme.typography.subheadline.subheadlineMetadata, bylines
theme.typography.footnote.footnoteAnnotations
theme.typography.caption.captionTimestamps, labels
theme.typography.caption2.caption2Legal, fine print

Spacing (4pt grid) #

TokenValueSemantic Alias
.xxs2pt
.xs4pt
.sm8pt.compact
.md12pt.gap
.lg16pt.inset
.xl24pt.section
.xxl32pt
.xxxl48pt
.minTapTarget44ptHIG minimum

WCAG Compliance #

The default theme uses Apple’s semantic UIColor values which automatically meet WCAG AA (4.5:1 for text, 3:1 for large text) in both light and dark mode.

For custom themes, use the included contrast checker:

// Runtime check
let ratio = ContrastCheck.ratio(
    between: UIColor.label,
    and: UIColor.systemBackground
)
// → ~17:1 in light mode

let passes = ContrastCheck.passes(
    foreground: UIColor(myCustomColor),
    background: UIColor.systemBackground,
    level: .normalAA
)

// Debug overlay
Text("Hello")
    .contrastDebug(
        foreground: .label,
        background: .systemBackground
    )

Creating a Theme #

Only name, primary, secondary, and accent are required. Everything else has sensible defaults from the protocol extension.

Color-only theme (minimal) #

struct ForestTheme: Theme {
    let name = "Forest"
    let primary: Color = .green
    let secondary: Color = Color(red: 0.4, green: 0.5, blue: 0.3)
    let accent: Color = Color(red: 0.2, green: 0.7, blue: 0.4)
}

With a different font design #

struct RoundedTheme: Theme {
    let name = "Rounded"
    let primary: Color = .purple
    let secondary: Color = .pink
    let accent: Color = .indigo

    var typography: ThemeTypography { .system(.rounded) }
}

With a custom font family #

struct EditorialTheme: Theme {
    let name = "Editorial"
    let primary: Color = .brown
    let secondary: Color = Color(red: 0.6, green: 0.5, blue: 0.4)
    let accent: Color = .orange

    // Uses "Georgia" at Apple's standard point sizes,
    // with relativeTo: for Dynamic Type scaling.
    var typography: ThemeTypography { .custom("Georgia") }
}

With selective overrides #

struct OceanTheme: Theme {
    let name = "Ocean"
    let primary: Color = Color(red: 0.0, green: 0.48, blue: 0.80)
    let secondary: Color = Color(red: 0.40, green: 0.60, blue: 0.70)
    let accent: Color = Color(red: 0.0, green: 0.78, blue: 0.75)

    // Override just the status colors that differ.
    let success: Color = Color(red: 0.0, green: 0.70, blue: 0.50)
    let info: Color = Color(red: 0.0, green: 0.48, blue: 0.80)
}