Getting Started/Configuration

Configuration

SwiftDS works out of the box with zero configuration. If you want to customise colours, spacing, radius, shadows, or animations to match your brand, all changes happen in one place — the DSTokens.swift file.

How tokens work

Every component in the system reads from the token enums — never from hardcoded values. This means you can change your brand colour once in DSColor and it will propagate instantly to every button, input, badge, and chart across the entire app.

DSTokens.swift — DSColor (customise your brand)
public enum DSColor {
    // Primary — warm sand
    public static let primary         = Color(hex: "#C4A882")
    public static let primaryHover    = Color(hex: "#B09872")
    public static let primaryMuted    = Color(hex: "#F5F0E8")
    public static let primaryFG       = Color.white

    // Secondary — warm slate
    public static let secondary       = Color(hex: "#6B6560")
    public static let secondaryHover  = Color(hex: "#5A5450")
    public static let secondaryMuted  = Color(hex: "#F1EDE8")

    // Accent — same family as primary
    public static let accent          = Color(hex: "#C4A882")
    public static let accentMuted     = Color(hex: "#F5F0E8")

    // Backgrounds
    public static let background      = Color(hex: "#FAFAFA")
    public static let backgroundDark  = Color(hex: "#0E0E0C")
    public static let surface         = Color(hex: "#FFFFFF")
    public static let surfaceDark     = Color(hex: "#161613")
    public static let surfaceElevated = Color(hex: "#FAFAF9")
    public static let surfaceElevatedDark = Color(hex: "#1E1E1B")

    // Borders and text
    public static let border          = Color(hex: "#E8E8E8")
    public static let borderDark      = Color(hex: "#2C2B28")
    public static let borderSubtle    = Color(hex: "#F0F0F0")
    public static let borderSubtleDark = Color(hex: "#252420")
    public static let foreground      = Color(hex: "#1A1816")
    public static let foregroundDark  = Color(hex: "#F0EDE8")
    public static let muted           = Color(hex: "#5A5650")
    public static let subtle          = Color(hex: "#9A9690")

    // Semantic
    public static let success         = Color(hex: "#22C55E")
    public static let warning         = Color(hex: "#F59E0B")
    public static let error           = Color(hex: "#EF4444")
    public static let info            = Color(hex: "#3B82F6")
}

Customising spacing

The spacing scale follows a 4pt baseline grid. If your product uses a different base unit (e.g. 8pt), change the base multipliers in DSSpacing.

DSTokens.swift — DSSpacing
public enum DSSpacing {
    public static let xs: CGFloat = 4
    public static let sm: CGFloat = 8
    public static let md: CGFloat = 16
    public static let lg: CGFloat = 24
    public static let xl: CGFloat = 32
    public static let xxl: CGFloat = 48
    public static let xxxl: CGFloat = 64

    // Usage:
    // VStack(spacing: DSSpacing.md) { ... }
    // .padding(DSSpacing.xl)
}

Customising border radius

Radius tokens are centralised in DSRadius. The package currently uses semantic names rather than short aliases.

DSTokens.swift — DSRadius
public enum DSRadius {
    public static let small: CGFloat = 6
    public static let medium: CGFloat = 10
    public static let large: CGFloat = 16
    public static let xl: CGFloat = 24
    public static let pill: CGFloat = 9999
    public static let showcaseCard: CGFloat = 16
}

Customising shadows

Elevation is also tokenised. Instead of repeating shadow values inline, components can share the same DSShadow presets.

DSTokens.swift — DSShadow
public struct DSShadow {
    public static let none    = DSShadow(color: .clear, radius: 0, x: 0, y: 0)
    public static let sm      = DSShadow(color: .black.opacity(0.06), radius: 4, x: 0, y: 2)
    public static let md      = DSShadow(color: .black.opacity(0.08), radius: 8, x: 0, y: 4)
    public static let lg      = DSShadow(color: .black.opacity(0.10), radius: 16, x: 0, y: 8)
    public static let xl      = DSShadow(color: .black.opacity(0.14), radius: 32, x: 0, y: 16)
    public static let primary = DSShadow(color: DSColor.primary.opacity(0.30), radius: 16, x: 0, y: 8)
    public static let glow    = DSShadow(color: DSColor.primary.opacity(0.50), radius: 24, x: 0, y: 0)
}

Customising animations

Animation behavior is centralised in DSAnimation. The current package exposes spring presets for interactive motion plus fade and pulse helpers.

DSTokens.swift — DSAnimation
public enum DSAnimation {
    // Buttons and toggles
    public static let interactive = Animation.spring(
        response: 0.3,
        dampingFraction: 0.7
    )

    // Modals, sheets, overlays
    public static let smooth = Animation.spring(
        response: 0.45,
        dampingFraction: 0.8
    )

    // List inserts and layout changes
    public static let gentle = Animation.spring(
        response: 0.6,
        dampingFraction: 0.85
    )

    // Opacity transitions and skeletons
    public static let fade  = Animation.easeOut(duration: 0.2)
    public static let pulse = Animation.easeInOut(duration: 1.0)
        .repeatForever(autoreverses: true)
}

Dark mode

SwiftDS uses explicit light and dark tokens together with DSColor.adaptive(light:dark:) and @Environment(\.colorScheme) internally. No additional setup is required — dark mode is automatic.

To force a specific mode in a subtree (e.g. always dark in a preview card), use the standard SwiftUI modifier:

Force a colour scheme on a subtree
DSCard {
    PreviewContent()
}
.preferredColorScheme(.dark) // always dark regardless of system setting

Building Your First Screen

Let's build a complete profile screen using SwiftDS components. This example demonstrates how to combine multiple components with proper spacing and layout:

ProfileScreen.swift — Complete example
import SwiftUI
import SwiftDS

struct ProfileScreen: View {
    @State private var name = "John Doe"
    @State private var email = "john@example.com"
    @State private var bio = ""
    @State private var notificationsEnabled = true
    @State private var darkModeEnabled = false
    
    var body: some View {
        ScrollView {
            VStack(spacing: DSSpacing.xl) {
                // Header
                VStack(spacing: DSSpacing.md) {
                    DSAvatar(initials: "JD", size: .xl, status: .online, tint: DSColor.primary)
                    
                    DSText(name, style: .title2)
                    
                    DSText(email, style: .bodyMuted)
                }
                .padding(.top, DSSpacing.xl)
                
                // Profile Info Card
                DSCard {
                    VStack(alignment: .leading, spacing: DSSpacing.md) {
                        DSText("Profile Information", style: .headline)
                        
                        DSTextField(
                            label: "Name",
                            placeholder: "Your name",
                            text: $name
                        )
                        
                        DSTextField(
                            label: "Email",
                            placeholder: "your@email.com",
                            text: $email,
                            leadingIcon: "envelope"
                        )
                        
                        DSTextArea(
                            label: "Bio",
                            placeholder: "Tell us about yourself...",
                            text: $bio,
                            minHeight: 100
                        )
                    }
                }
                
                // Settings Card
                DSCard {
                    VStack(alignment: .leading, spacing: DSSpacing.md) {
                        DSText("Preferences", style: .headline)
                        
                        DSToggle(
                            "Notifications",
                            description: "Receive push notifications",
                            isOn: $notificationsEnabled
                        )

                        DSToggle(
                            "Dark Mode",
                            description: "Use dark theme",
                            isOn: $darkModeEnabled
                        )
                    }
                }
                
                // Actions
                VStack(spacing: DSSpacing.sm) {
                    DSButton("Save Changes", variant: .primary, size: .lg) {
                        saveProfile()
                    }
                    
                    DSButton("Sign Out", variant: .ghost, size: .md) {
                        signOut()
                    }
                }
            }
            .padding(DSSpacing.lg)
        }
    }
    
    private func saveProfile() {
        print("Profile saved")
    }
    
    private func signOut() {
        print("Signed out")
    }
}

#Preview {
    ProfileScreen()
}

What We Used

  • DSAvatar - User profile picture with fallback
  • DSCard - Content containers with elevation
  • DSTextField - Text inputs with labels and icons
  • DSTextArea - Multi-line text input
  • DSToggle - On/off switches for settings
  • DSButton - Primary and ghost button variants
  • DSSpacing - Consistent spacing tokens throughout
  • DSText / DSTextStyle - Package typography helpers for text hierarchy

Using the Showcase app

The package ships with built-in showcase entry points so you can inspect tokens, components, and app-level examples interactively in previews or in the simulator.

Open in Xcode Previews
// Main package showroom
#Preview {
    DSShowcaseApp()
}

// Advanced/product showcase
#Preview {
    DSAdvancedShowcaseApp()
}