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.
| Token | Used for |
|---|---|
background | App canvas |
surface | Cards, inputs, tables, and presentations |
muted | Table headers, sidebars, skeletons, and secondary buttons |
accent / onAccent | Hover, selection, and message bubbles |
border | Quiet surface borders and separators |
input | Field boundaries |
ring | Focused field borders |
tint | Native switches, checkboxes, pickers, progress, tabs, and charts |
primary / onPrimary | Primary actions, selected dates, and badges |
foreground / mutedForeground | Body text and supporting text |
destructive / onDestructive | Errors 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.
| Class | Native font role |
|---|---|
text-xs | caption |
text-sm | subheadline |
text-base | body |
text-lg | title3 |
text-xl | title2 |
text-2xl | title |
text-3xl | largeTitle |
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.