swiftcnearly preview

Themes

Adaptive colors and native typography through the SwiftUI environment.

var theme = TWTheme(spacingUnit: 5)
theme.colors[.primary] = TWAdaptiveColor(light: .indigo, dark: .mint)
theme.colors[.onPrimary] = TWAdaptiveColor(light: .white, dark: .black)
theme.colors[.tint] = theme.colors[.primary]
theme.colors[.ring] = theme.colors[.primary]
theme.radii[.lg] = 16

ContentView()
    .twTheme(theme)

Semantic colors resolve through the current color scheme. The theme inherits through the SwiftUI environment and supports local overrides. Partial theme initializers preserve unspecified defaults.

Component colors

The default theme uses cool neutral surfaces, quiet separators, and stronger field boundaries. Dark surfaces use several levels of charcoal so cards, menus, and sidebars remain distinct. Text keeps sufficient contrast on these surfaces. Hover and selection use a separate fill from static decoration.

TokenUsed for
backgroundApp canvas
surfaceCards, inputs, tables, and presentations
mutedTable headers, sidebars, skeletons, and secondary buttons
accent / onAccentHover, selection, and message bubbles
borderQuiet surface borders and separators
inputField boundaries
ringFocused field borders
tintNative switches, checkboxes, pickers, progress, tabs, and charts
primary / onPrimaryPrimary actions, selected dates, and badges
foreground / mutedForegroundBody text and supporting text
destructive / onDestructiveErrors and destructive actions

tint and ring default to SwiftUI's Color.accentColor, which uses your app's accent color. Set them explicitly in your theme when you want a shared brand color, as in the example above. Native control styles decide how tint appears. macOS also dims controls in inactive windows. Color tokens remain independent. Changing primary alone does not change native controls or focus borders. When you change a filled background, choose its paired foreground for both appearances too.

The theme inherits into imported components and copied source in the same subtree. For a local override, use classes: "bg-muted" or classes: "cn-tint-[primary]". You can also change any named component recipe in global rules. For example, this changes all secondary buttons in the subtree:

ContentView()
    .twRules {
        $0.named["button-secondary"] = TWStyle(
            TWStyle.defaultStyle(for: "button-secondary")!,
            "bg-accent text-onAccent hover:opacity-90"
        )
    }

Native layout tokens

Read theme values for native layouts:

@Environment(\.twTheme) private var theme

VStack(spacing: theme.space(4)) {
    Text("First")
    Text("Second")
}

Custom colors

Add custom semantic colors through ordinary Swift extensions:

extension TWColor {
    static let brand = TWColor("brand")
}

let theme = TWTheme(colors: [
    .brand: TWAdaptiveColor(light: .purple, dark: .pink)
])

Text("Brand").tw(.fg(.brand)).twTheme(theme)

Register custom colors in the theme before using them. Typed color utilities fall back to SwiftUI's semantic primary foreground for an unregistered token. String color classes require registration; an unknown token rejects the complete style for that surface. See color troubleshooting.

Scale and typography

The standard spacing unit is four native points. p-4 selects 16 points. Bracket lengths such as p-[16] ignore the theme spacing scale.

ClassNative font role
text-xscaption
text-smsubheadline
text-basebody
text-lgtitle3
text-xltitle2
text-2xltitle
text-3xllargeTitle

These typography tokens use scalable native font roles. Explicit text-[18] uses a fixed system font size.

Global rules let you share recipes, utility factories, and animation presets.

On this page