Stop Rebuilding the Same SwiftUI Components: A Guide to DesignFoundation NerdSnipe Inc. released DesignFoundation, an open-source SwiftUI package that provides a token-based theming engine, 25 components, and 6 feedback/overlay modifiers to prevent UI drift when building with AI coding agents. The package includes CLAUDE.md, AGENTS.md, and Cursor rules so agents consistently use DFButton instead of creating new button styles. DesignFoundation ships with four presets—slate, aurora, copper, and sage—and requires Xcode 16+, iOS 18+, or macOS 15+. "I'll just write a quick button style... and a text field with validation... and a card component..." Two weeks later you have a bespoke design system that only half the app uses, three slightly different buttons, and nothing shipped. If you're building with an AI coding agent, the drift problem is worse, not better. Ask Claude, Cursor, or Codex to "add a settings screen" three separate times and you'll get three different paddings, three different corner radii, and a button style that quietly forked itself somewhere around commit 40. Agents are great at writing plausible SwiftUI and bad at remembering what the rest of your app already decided. DesignFoundation https://github.com/NerdSnipe-Inc/design-foundation is an open-source SwiftUI package that gives you a token-based theming engine, 25 components, and 6 feedback/overlay modifiers that all read from the same theme. You set it once at the app root, and everything underneath updates. That's the whole idea — and it ships with CLAUDE.md , AGENTS.md , and Cursor rules already written, so an agent working in your repo reaches for DFButton instead of inventing a new one. In this tutorial you'll go from zero to a themed, consistent SwiftUI UI — without writing a single custom button style. By the end you'll have: Requirements: Xcode 16+, iOS 18+ / macOS 15+ target, Swift 6. https://github.com/NerdSnipe-Inc/design-foundation 1.0.0 DesignFoundation to your target dependencies: .package url: "https://github.com/NerdSnipe-Inc/design-foundation", from: "1.0.0" , targets: .target name: "YourApp", dependencies: "DesignFoundation" Every app that grows past a prototype ends up with some version of a ThemeManager singleton — a global object holding colors and fonts that every view reaches into, and that somebody eventually has to thread through previews, tests, and SwiftUI's environment by hand. Import DesignFoundation and attach a preset theme to your root scene instead. That's all the wiring needed — every component below this point reads from it. python import SwiftUI import DesignFoundation @main struct MyApp: App { var body: some Scene { WindowGroup { ContentView .dfThemePreset .slate } } } Four general-purpose presets ship out of the box: | Preset | Character | Primary light / dark | Works well for | |---|---|---|---| .slate | Professional, balanced, default radius | 1C3D5A / 64B5F6 | SaaS dashboards, dev tools | .aurora | Vibrant, rounded, electric violet | 6C47FF / A78BFA | Creative tools, social apps | .copper | Warm, editorial, sharp radius | C4622D / F4A261 | Finance, content readers | .sage | Calm, organic, very rounded | 2D6A4F / 74C69D | Health, wellness | Each preset automatically switches between its light and dark variant based on @Environment \.colorScheme . You don't manage that yourself. The second conversation every SwiftUI project has with itself: "should this button be .borderedProminent or a custom ButtonStyle , and does the disabled state look right, and did I remember to match the corner radius everywhere else?" You answer those questions once, then answer them again for the next button. Once the theme is in the environment, you can drop in any DF component instead. They all read tokens from the theme — no manual color or font passing required. js struct ContentView: View { var body: some View { VStack spacing: 24 { DFText "Welcome back", scale: .title DFText "Sign in to continue", scale: .body DFButton "Sign in" { // action } } .padding } } DFText uses the theme's typography tokens. DFButton uses color, radius, and spacing tokens. Change the preset at the app root and both update. DFButton ships with five built-in styles: DFButton "Primary action" { } // .filled default DFButton "Secondary" { } .dfButtonStyle .outlined DFButton "Subtle" { } .dfButtonStyle .ghost DFButton "Tinted" { } .dfButtonStyle .tinted DFButton "Glass" { } .dfButtonStyle .glass // requires iOS 26+ / macOS 26+ Apply a style to an entire section instead of to each button individually: VStack { DFButton "Save" { } DFButton "Cancel" { } } .dfButtonStyle .outlined Validation UI is deceptively fiddly: where does the error text sit, does it push the layout down or overlay it, does the border turn red before or after the user leaves the field. Most teams solve this differently in every form in the app. DesignFoundation's input components share a DFValidationState type so error states look consistent everywhere. js struct SignInView: View { @State private var email = "" @State private var password = "" @State private var emailState: DFValidationState = .none @State private var passwordState: DFValidationState = .none var body: some View { VStack spacing: 16 { DFTextField "Email", text: $email, validationState: emailState .dfTextFieldStyle .outlined DFSecureField "Password", text: $password, validationState: passwordState .dfTextFieldStyle .outlined DFButton "Sign in" { validate } } .padding } private func validate { emailState = email.contains "@" ? .valid : .error "Enter a valid email address" passwordState = password.count = 8 ? .valid : .error "Password must be at least 8 characters" } } DFValidationState has three cases: .none , .valid , and .error String . The error message renders inline under the field automatically — you don't lay it out yourself. DFSecureField also includes a built-in reveal toggle. No extra code needed. "What shadow radius did we use for cards again?" is a question that shouldn't need a Slack search. DFCard reads elevation from the same theme as everything else, so every card in the app — dashboard tile, list row, modal — gets the same shadow, radius, and surface color without you copy-pasting a .shadow modifier around the codebase. DFCard { VStack alignment: .leading, spacing: 12 { DFText "Account summary", scale: .headline DFDivider HStack { DFText "Status", scale: .body Spacer DFBadge text: "Active" .dfBadgeStyle .tinted } } .padding } .dfCardStyle .elevated Card styles: .elevated , .outlined , .filled , .glass iOS 26+ . Toast queues in particular tend to grow their own bespoke state machine — an array of pending messages, timers to dismiss them, logic to avoid three toasts stacking on top of each other. DesignFoundation ships that machinery so you don't own it. Mount .dfToast once on any root view, then trigger toasts imperatively from anywhere in the tree: // Mount once at the root ContentView .dfToast // Trigger from any view action DFButton "Save changes" { // do the save DFToastQueue.shared.show text: "Changes saved", severity: .success } DFToastQueue handles queue management and auto-dismiss. Multiple toasts queued in quick succession display in order. if isLoading { DFSkeleton .frame height: 80 } else { // actual content } DFSkeleton plays a shimmer animation. Use it as a placeholder while data loads. DFProgressBar value: uploadProgress // linear, determinate DFProgressBar variant: .indeterminate // indeterminate The four presets cover a lot of ground, but sometimes you need your brand colors. DFTheme exposes token namespaces for colors, typography, spacing, radius, shadow, and animation. js @main struct MyApp: App { var body: some Scene { WindowGroup { ContentView .dfTheme DFTheme colors: DFColorTokens primary: Color red: 0.388, green: 0.400, blue: 0.945 // brand indigo, 6366F1 , spacing: DFSpacingTokens md: 20 , radius: DFRadiusTokens md: 14 } } } DesignFoundation doesn't ship a Color hex: initializer — use Color red:green:blue: , an asset catalog color, or your own hex-to- Color helper if you're pasting values straight from a design tool. You don't have to specify every token. Unspecified tokens fall back to the defaults. If a preset is 95% right but one token is off, mutate just that token: js var theme = DFTheme.slateLight theme.colors.primary = .purple MyView .dfTheme theme The preset modifier handles switching automatically. If you need to force a specific variant — useful in Previews or sub-tree overrides — use the named theme directly: // Force dark regardless of system setting MyView .dfTheme .slateDark Because DFTheme lives in SwiftUI's environment, you can scope overrides to any part of the view tree: VStack { // rest of the UI uses the app-level theme SettingsPanel .dfTheme .copperLight // this subtree uses copper } The alternative to a custom preset is remembering to apply your brand color twice — once for light mode, once for dark — everywhere you set a theme. DFThemePreset collapses that into one named value: js let brandPreset = DFThemePreset light: { var t = DFTheme.slateLight t.colors.primary = Color red: 0.388, green: 0.400, blue: 0.945 // 6366F1 return t } , dark: { var t = DFTheme.slateDark t.colors.primary = Color red: 0.506, green: 0.549, blue: 0.973 // 818CF8 return t } MyApp .dfThemePreset brandPreset Without a shared style system, "make every button on this screen ghost-style" means visiting every button. With one, it's one modifier on the container. The style pattern follows the same protocol SwiftUI uses for ButtonStyle . Styles compose, propagate through the environment, and apply hierarchically. // Apply styles to a whole section VStack { ... } .dfButtonStyle .outlined .dfCardStyle .glass // Override for one component DFButton "Delete", role: .destructive { } .dfButtonStyle .ghost If you want Liquid Glass across your entire UI iOS 26+ / macOS 26+ : ContentView .dfButtonStyle .glass .dfCardStyle .glass .dfTooltipStyle .glass DesignFoundation targets iOS 18+, macOS 15+, and visionOS 2+. Most components work across all three. A few notes: .glass styles require iOS 26+ / macOS 26+ Liquid Glass . You get a compile-time reminder if you use them on an older target. DFSidebar is only meaningful on macOS and iPadOS. DFPlatformVariant .automatic / .compact / .expanded / .immersive lets components adapt at runtime, or lets you force a layout for Previews and testing.The part worth calling out: you do not need if os macOS / if os iOS to use any DF component. Platform differences — sidebar vs. tab bar, desktop vs. touch density, native table vs. scrollable rows — are handled internally by DFPlatformContext , injected automatically by .dfTheme . Your own view code stays platform-agnostic; the only place you still reach for a guard is app-level APIs the package doesn't wrap, like a Mac-only WindowGroup .If you're using Claude Code, Cursor, or another agent to build UI, DesignFoundation ships instructions for all three: CLAUDE.md , AGENTS.md , and .cursor/rules/design-foundation.mdc sit at the package root. Point an agent at a repo that depends on DesignFoundation and it already knows the rule — reach for DFButton , DFCard , DFTextField , not a hand-rolled equivalent — without you writing that instruction yourself. That matters more for agents than for you. A human developer remembers the button style they used last week; an agent starting a fresh session doesn't, and will happily invent a fourth ButtonStyle if nothing tells it otherwise. Combined with the platform-agnostic API above, this means an agent can build a real cross-platform screen — no if os blocks, no reinvented button — in one pass, and the result is something you can actually debug later, because every screen it touched is made of the same handful of known components instead of N slightly different one-offs. A quick reference of everything that ships: Primitives: DFButton , DFText , DFIcon , DFBadge , DFAvatar , DFDivider Inputs: DFTextField , DFSecureField , DFTextArea , DFToggle , DFSlider , DFPicker , DFDatePicker , DFCheckbox Layout: DFCard Overlays modifiers, not constructible views : .dfModal , .dfSheet , .dfPopover , .dfTooltip Navigation: DFTabBar , DFNavigationBar , DFSidebar Supplementary: DFAlertConfiguration + .dfAlert , DFToastQueue + .dfToast , DFSkeleton , DFProgressBar , DFList , DFListRow , DFTable , DFDataTable , DFDataGrid DesignFoundation solves the problem most SwiftUI projects hit at month three: component drift, where three different developers have built three slightly different buttons and nobody's sure which one is "correct" anymore. When every component reads from the same DFTheme in the environment, a brand refresh is a token edit, not a project-wide find-and-replace. The package is MIT licensed and actively maintained. The GitHub repo https://github.com/NerdSnipe-Inc/design-foundation is the right place for issues or feedback, especially if the theme API feels rough for your use case. If you need pre-built screens auth flows, dashboards, CRM layouts composed from these same primitives, there's a pro tier https://nerdsnipe-inc.github.io/design-foundation/pro/ — but the primitives in this tutorial stay free. Have a question about a specific component or want to see a more advanced theming example? Drop it in the comments.