11 KiB
Design System
Colors, typography, spacing, and visual patterns for professional macOS apps.
<semantic_colors>
import SwiftUI
extension Color {
// Use semantic colors that adapt to light/dark mode
static let background = Color(NSColor.windowBackgroundColor)
static let secondaryBackground = Color(NSColor.controlBackgroundColor)
static let tertiaryBackground = Color(NSColor.underPageBackgroundColor)
// Text
static let primaryText = Color(NSColor.labelColor)
static let secondaryText = Color(NSColor.secondaryLabelColor)
static let tertiaryText = Color(NSColor.tertiaryLabelColor)
static let quaternaryText = Color(NSColor.quaternaryLabelColor)
// Controls
static let controlAccent = Color.accentColor
static let controlBackground = Color(NSColor.controlColor)
static let selectedContent = Color(NSColor.selectedContentBackgroundColor)
// Separators
static let separator = Color(NSColor.separatorColor)
static let gridLine = Color(NSColor.gridColor)
}
// Usage
Text("Hello")
.foregroundStyle(.primaryText)
.background(.background)
</semantic_colors>
<custom_colors>
extension Color {
// Define once, use everywhere
static let appPrimary = Color("AppPrimary") // From asset catalog
static let appSecondary = Color("AppSecondary")
// Or programmatic
static let success = Color(red: 0.2, green: 0.8, blue: 0.4)
static let warning = Color(red: 1.0, green: 0.8, blue: 0.0)
static let error = Color(red: 0.9, green: 0.3, blue: 0.3)
}
// Asset catalog with light/dark variants
// Assets.xcassets/AppPrimary.colorset/Contents.json:
/*
{
"colors" : [
{
"color" : { "color-space" : "srgb", "components" : { "red" : "0.2", "green" : "0.5", "blue" : "1.0" } },
"idiom" : "universal"
},
{
"appearances" : [ { "appearance" : "luminosity", "value" : "dark" } ],
"color" : { "color-space" : "srgb", "components" : { "red" : "0.4", "green" : "0.7", "blue" : "1.0" } },
"idiom" : "universal"
}
]
}
*/
</custom_colors>
```swift extension Font { // System fonts static let displayLarge = Font.system(size: 34, weight: .bold, design: .default) static let displayMedium = Font.system(size: 28, weight: .semibold) static let displaySmall = Font.system(size: 22, weight: .semibold)static let headlineLarge = Font.system(size: 17, weight: .semibold)
static let headlineMedium = Font.system(size: 15, weight: .semibold)
static let headlineSmall = Font.system(size: 13, weight: .semibold)
static let bodyLarge = Font.system(size: 15, weight: .regular)
static let bodyMedium = Font.system(size: 13, weight: .regular)
static let bodySmall = Font.system(size: 11, weight: .regular)
// Monospace for code
static let codeLarge = Font.system(size: 14, weight: .regular, design: .monospaced)
static let codeMedium = Font.system(size: 12, weight: .regular, design: .monospaced)
static let codeSmall = Font.system(size: 10, weight: .regular, design: .monospaced)
}
// Usage Text("Title") .font(.displayMedium)
Text("Body text") .font(.bodyMedium)
Text("let x = 42") .font(.codeMedium)
</typography>
<spacing>
```swift
enum Spacing {
static let xxxs: CGFloat = 2
static let xxs: CGFloat = 4
static let xs: CGFloat = 8
static let sm: CGFloat = 12
static let md: CGFloat = 16
static let lg: CGFloat = 24
static let xl: CGFloat = 32
static let xxl: CGFloat = 48
static let xxxl: CGFloat = 64
}
// Usage
VStack(spacing: Spacing.md) {
Text("Title")
Text("Subtitle")
}
.padding(Spacing.lg)
HStack(spacing: Spacing.sm) {
Image(systemName: "star")
Text("Favorite")
}
<corner_radius>
enum CornerRadius {
static let small: CGFloat = 4
static let medium: CGFloat = 8
static let large: CGFloat = 12
static let xlarge: CGFloat = 16
}
// Usage
RoundedRectangle(cornerRadius: CornerRadius.medium)
.fill(.secondaryBackground)
Text("Tag")
.padding(.horizontal, Spacing.sm)
.padding(.vertical, Spacing.xxs)
.background(.controlBackground, in: RoundedRectangle(cornerRadius: CornerRadius.small))
</corner_radius>
```swift extension View { func cardShadow() -> some View { shadow(color: .black.opacity(0.1), radius: 4, x: 0, y: 2) }func elevatedShadow() -> some View {
shadow(color: .black.opacity(0.15), radius: 8, x: 0, y: 4)
}
func subtleShadow() -> some View {
shadow(color: .black.opacity(0.05), radius: 2, x: 0, y: 1)
}
}
// Usage CardView() .cardShadow()
</shadows>
<component_styles>
<buttons>
```swift
struct PrimaryButtonStyle: ButtonStyle {
func makeBody(configuration: Configuration) -> some View {
configuration.label
.font(.headlineMedium)
.foregroundStyle(.white)
.padding(.horizontal, Spacing.md)
.padding(.vertical, Spacing.sm)
.background(
RoundedRectangle(cornerRadius: CornerRadius.medium)
.fill(Color.accentColor)
)
.opacity(configuration.isPressed ? 0.8 : 1.0)
}
}
struct SecondaryButtonStyle: ButtonStyle {
func makeBody(configuration: Configuration) -> some View {
configuration.label
.font(.headlineMedium)
.foregroundStyle(.accentColor)
.padding(.horizontal, Spacing.md)
.padding(.vertical, Spacing.sm)
.background(
RoundedRectangle(cornerRadius: CornerRadius.medium)
.stroke(Color.accentColor, lineWidth: 1)
)
.opacity(configuration.isPressed ? 0.8 : 1.0)
}
}
// Usage
Button("Save") { save() }
.buttonStyle(PrimaryButtonStyle())
Button("Cancel") { cancel() }
.buttonStyle(SecondaryButtonStyle())
extension View { func cardStyle() -> some View { modifier(CardStyle()) } }
// Usage VStack { Text("Card Title") Text("Card content") } .cardStyle()
</cards>
<list_rows>
```swift
struct ItemRow: View {
let item: Item
let isSelected: Bool
var body: some View {
HStack(spacing: Spacing.sm) {
Image(systemName: item.icon)
.foregroundStyle(isSelected ? .white : .secondaryText)
VStack(alignment: .leading, spacing: Spacing.xxs) {
Text(item.name)
.font(.headlineSmall)
.foregroundStyle(isSelected ? .white : .primaryText)
Text(item.subtitle)
.font(.bodySmall)
.foregroundStyle(isSelected ? .white.opacity(0.8) : .secondaryText)
}
Spacer()
Text(item.date.formatted(date: .abbreviated, time: .omitted))
.font(.bodySmall)
.foregroundStyle(isSelected ? .white.opacity(0.8) : .tertiaryText)
}
.padding(.horizontal, Spacing.sm)
.padding(.vertical, Spacing.xs)
.background(
RoundedRectangle(cornerRadius: CornerRadius.small)
.fill(isSelected ? Color.accentColor : .clear)
)
}
}
</list_rows>
<text_fields>
struct StyledTextField: View {
let placeholder: String
@Binding var text: String
var body: some View {
TextField(placeholder, text: $text)
.textFieldStyle(.plain)
.font(.bodyMedium)
.padding(Spacing.sm)
.background(
RoundedRectangle(cornerRadius: CornerRadius.medium)
.fill(.controlBackground)
)
.overlay(
RoundedRectangle(cornerRadius: CornerRadius.medium)
.stroke(.separator, lineWidth: 1)
)
}
}
</text_fields> </component_styles>
```swift // Use SF Symbols Image(systemName: "doc.text") Image(systemName: "folder.fill") Image(systemName: "gear")// Consistent sizing Image(systemName: "star") .font(.system(size: 16, weight: .medium))
// With colors Image(systemName: "checkmark.circle.fill") .symbolRenderingMode(.hierarchical) .foregroundStyle(.green)
// Multicolor Image(systemName: "externaldrive.badge.checkmark") .symbolRenderingMode(.multicolor)
</icons>
<animations>
```swift
// Standard durations
enum AnimationDuration {
static let fast: Double = 0.15
static let normal: Double = 0.25
static let slow: Double = 0.4
}
// Common animations
extension Animation {
static let defaultSpring = Animation.spring(response: 0.3, dampingFraction: 0.7)
static let quickSpring = Animation.spring(response: 0.2, dampingFraction: 0.8)
static let gentleSpring = Animation.spring(response: 0.5, dampingFraction: 0.7)
static let easeOut = Animation.easeOut(duration: AnimationDuration.normal)
static let easeIn = Animation.easeIn(duration: AnimationDuration.normal)
}
// Usage
withAnimation(.defaultSpring) {
isExpanded.toggle()
}
// Respect reduce motion
struct AnimationSettings {
static var prefersReducedMotion: Bool {
NSWorkspace.shared.accessibilityDisplayShouldReduceMotion
}
static func animation(_ animation: Animation) -> Animation? {
prefersReducedMotion ? nil : animation
}
}
<dark_mode>
// Automatic adaptation
struct ContentView: View {
@Environment(\.colorScheme) var colorScheme
var body: some View {
VStack {
// Semantic colors adapt automatically
Text("Title")
.foregroundStyle(.primaryText)
.background(.background)
// Manual override when needed
Image("logo")
.colorInvert() // Only if needed
}
}
}
// Force scheme for preview
#Preview("Dark Mode") {
ContentView()
.preferredColorScheme(.dark)
}
</dark_mode>
```swift // Dynamic type support Text("Title") .font(.headline) // Scales with user settings// Custom fonts with scaling @ScaledMetric(relativeTo: .body) var customSize: CGFloat = 14 Text("Custom") .font(.system(size: customSize))
// Contrast Button("Action") { } .foregroundStyle(.white) .background(.accentColor) // Ensure contrast ratio >= 4.5:1
// Reduce transparency @Environment(.accessibilityReduceTransparency) var reduceTransparency
VStack { // content } .background(reduceTransparency ? .background : .background.opacity(0.8))
</accessibility>