Contents

philiprehberger/swift-design-system

Token-based design system engine with theme switching, component registry, and JSON export

Requirements

  • Swift >= 6.0
  • macOS 13+ / iOS 16+ / tvOS 16+ / watchOS 9+

Installation

Add to your Package.swift:

dependencies: [
    .package(url: "https://github.com/philiprehberger/swift-design-system.git", from: "0.2.0")
]

Then add "DesignSystem" to your target dependencies:

.target(name: "YourTarget", dependencies: [
    .product(name: "DesignSystem", package: "swift-design-system")
])

Usage

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"

Themes

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"

Theme Observers

React to theme changes:

ds.themes.onChange { theme in
    print("Switched to \(theme.name)")
}

Component Registry

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"

Design Tokens

// 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)

JSON Export / Import (Figma Bridge)

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)

Shadows & Borders

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

Theme Inheritance

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)

Token Validation

let issues = TokenValidator.validate(
    theme,
    requiredColors: ["primary", "background"],
    requiredSpacing: ["sm", "md", "lg"]
)
for issue in issues {
    print("[\(issue.severity)] \(issue.message)")
}

Theme Merging

Extend a base theme with overrides:

let brand = base.merging(Theme(
    name: "brand",
    colors: ["primary": ColorToken(hex: "#FF6600")!]
))
// Inherits all base tokens, overrides "primary"

API

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 |

Development

swift build
swift test

Support

If you find this project useful:

⭐ Star the repo

πŸ› Report issues

πŸ’‘ Suggest features

❀️ Sponsor development

🌐 All Open Source Projects

πŸ’» GitHub Profile

πŸ”— LinkedIn Profile

License

MIT

Package Metadata

Repository: philiprehberger/swift-design-system

Default branch: main

README: README.md