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.
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.
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.
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.
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.
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:
DSCard {
PreviewContent()
}
.preferredColorScheme(.dark) // always dark regardless of system settingBuilding 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:
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.
// Main package showroom
#Preview {
DSShowcaseApp()
}
// Advanced/product showcase
#Preview {
DSAdvancedShowcaseApp()
}