Token-based design system engine with theme switching, component registry, and JSON export
.package(url: "https://github.com/philiprehberger/swift-design-system.git", from: "0.2.0")Token-based design system engine with theme switching, component registry, and JSON export
Add to your Package.swift:
dependencies: [
.package(url: "https://github.com/philiprehberger/swift-design-system.git", from: "0.1.0")
]
Then add "DesignSystem" to your target dependencies:
.target(name: "YourTarget", dependencies: [
.product(name: "DesignSystem", package: "swift-design-system")
])
import DesignSystem
let ds = DesignSystem.shared
ds.themes.register(Theme(
name: "light",
colors: ["primary": ColorToken(hex: "#0066FF")!],
spacing: ["md": SpacingToken(16)],
typography: ["body": TypographyToken(fontSize: 16, fontWeight: .regular)]
))
ds.color("primary")?.hex // "#0066FF"
Define and switch between named themes:
let light = Theme(
name: "light",
colors: ["bg": ColorToken(hex: "#FFFFFF")!, "text": ColorToken(hex: "#111111")!],
spacing: ["sm": SpacingToken(8), "md": SpacingToken(16), "lg": SpacingToken(32)],
typography: ["heading": TypographyToken(fontSize: 24, fontWeight: .bold, lineHeight: 32)]
)
let dark = Theme(
name: "dark",
colors: ["bg": ColorToken(hex: "#111111")!, "text": ColorToken(hex: "#FFFFFF")!],
spacing: light.spacing,
typography: light.typography
)
ds.themes.register(light)
ds.themes.register(dark)
ds.themes.switchTo("dark")
ds.color("bg")?.hex // "#111111"
React to theme changes:
ds.themes.onChange { theme in
print("Switched to \(theme.name)")
}
Register reusable component style configurations:
ds.components.register(ComponentStyle(
name: "button.primary",
properties: ["backgroundColor": "#0066FF", "cornerRadius": "8", "textColor": "#FFFFFF"]
))
let style = ds.components.style(named: "button.primary")
style?.properties["backgroundColor"] // "#0066FF"
// Colors with hex parsing
let color = ColorToken(hex: "#FF5733")!
color.red // 1.0
color.hex // "#FF5733"
// Spacing
let spacing = SpacingToken(16)
// Typography
let heading = TypographyToken(fontSize: 24, fontWeight: .bold, lineHeight: 32, letterSpacing: -0.5)
Export tokens for handoff to designers, or import from Figma:
// Export
let json = try ds.exportActiveTheme()
let jsonString = String(data: json, encoding: .utf8)!
// Import
let imported = ds.importTheme(from: jsonData)
ds.themes.switchTo(imported!.name)
let theme = Theme(
name: "material",
shadows: ["card": ShadowToken(color: ColorToken(hex: "#000000")!, radius: 8, yOffset: 4, opacity: 0.2)],
borders: ["input": BorderToken(width: 1, color: ColorToken(hex: "#CCCCCC")!)]
)
theme.shadow("card")?.radius // 8
theme.border("input")?.width // 1
let base = Theme(name: "base", colors: ["bg": ColorToken(hex: "#FFFFFF")!, "text": ColorToken(hex: "#000000")!])
let dark = base.extending(Theme(name: "dark", colors: ["bg": ColorToken(hex: "#111111")!]))
dark.color("bg")?.hex // "#111111" (overridden)
dark.color("text")?.hex // "#000000" (inherited)
let issues = TokenValidator.validate(
theme,
requiredColors: ["primary", "background"],
requiredSpacing: ["sm", "md", "lg"]
)
for issue in issues {
print("[\(issue.severity)] \(issue.message)")
}
Extend a base theme with overrides:
let brand = base.merging(Theme(
name: "brand",
colors: ["primary": ColorToken(hex: "#FF6600")!]
))
// Inherits all base tokens, overrides "primary"
DesignSystem| Method | Description |
|---|---|
.shared | Singleton instance |
.themes | Access the ThemeManager |
.components | Access the ComponentRegistry |
.color(_:) | Get color from active theme |
.spacing(_:) | Get spacing from active theme |
.typography(_:) | Get typography from active theme |
.exportActiveTheme() | Export active theme as JSON |
.importTheme(from:) | Import theme from JSON data |
Theme| Method | Description |
|---|---|
.color(_:) | Look up a named color token |
.spacing(_:) | Look up a named spacing token |
.typography(_:) | Look up a named typography token |
.merging(_:) | Merge with another theme |
.shadow(_:) | Look up a shadow token |
.border(_:) | Look up a border token |
.extending(_:) | Create child theme with inherited tokens |
ShadowToken| Property | Description |
|---|---|
.color | Shadow color |
.radius | Blur radius |
.xOffset / .yOffset | Shadow offset |
.opacity | Shadow opacity (0-1) |
BorderToken| Property | Description |
|---|---|
.width | Border width in points |
.color | Border color |
.style | Line style (solid, dashed, dotted) |
TokenValidator| Method | Description |
|---|---|
.validate(_:requiredColors:requiredSpacing:requiredTypography:) | Lint a theme against requirements |
ThemeManager| Method | Description |
|---|---|
.register(_:) | Register a theme |
.switchTo(_:) | Switch active theme |
.activeTheme | Current theme |
.availableThemes | List theme names |
.onChange(_:) | Observe theme changes |
ComponentRegistry| Method | Description |
|---|---|
.register(_:) | Register a component style |
.style(named:) | Retrieve by name |
.allStyles | List style names |
.remove(_:) | Remove a style |
TokenExporter| Method | Description |
|---|---|
.exportJSON(_:) | Export theme to JSON data |
.importJSON(_:) | Import theme from JSON data |
.exportTheme(_:) | Export to dictionary |
.importTheme(from:) | Import from dictionary |
swift build
swift test
If you find this project useful: