{"slug": "stop-rebuilding-auth-onboarding-and-dashboards-designfoundationpro", "title": "Stop Rebuilding Auth, Onboarding, and Dashboards: DesignFoundationPro", "summary": "NerdSnipe Inc. released DesignFoundationPro, a commercial add-on to its DesignFoundation SwiftUI package that provides 29 blocks, 47 screens, 18 navigation shells, and 9 runnable composition examples for auth, onboarding, charts, and data tables. The package claims to reduce code by ~87% compared to building from scratch and includes guardrails to ensure consistent UI when used with AI coding agents. Pricing is $149 for an individual license or $449 for a team of up to five developers, with optional annual updates at $39.", "body_md": "Part 2 of 2.This tutorial builds on[Part 1 — DesignFoundation core]. If you haven't added the base package and theme yet, start there.\n\nThe core package gives you tokens and primitives. **DesignFoundationPro** adds what comes next — 29 blocks, 47 screens, 18 navigation shells, and 9 runnable composition examples across auth, onboarding, charts, data tables, and full product verticals. The docs claim ~87% fewer lines of code versus building from scratch. That's the bet.\n\nThis matters even more if an AI coding agent is doing the building. Ask an agent for a CRM screen and a settings screen in the same session and, without guardrails, you'll get two different takes on spacing, two different sidebar behaviors, and a table that's native on Mac in one screen and a scroll view pretending to be a table in the other. Pro ships with that guardrail already in place — more on that in Where to go from here.\n\nPro sits on top of Foundation and re-exports it. Import only `DesignFoundationPro`\n\nand you get everything from both:\n\n```\nYourApp                    your models, data, routing\nDesignFoundationPro        29 blocks · 47 screens · 18 shells · 9 examples\nDesignFoundation           tokens · primitives · validation · theme engine · MIT\n```\n\n**Access:** Foundation is MIT and public. Pro is a commercial add-on — repo access is granted after purchase. Licenses are **lifetime** (no subscription); annual updates are $39/year and entirely optional. Pricing: **$149** individual · **$449** team (up to 5 devs).\n\nBefore buying, browse everything in **DFPlayground** — a free macOS app that lets you preview all 29 blocks, 47 screens, and 18 shells with live theming.\n\nPro declares Foundation as its own dependency and re-exports it via `@_exported import DesignFoundation`\n\n. Add only the Pro package — Foundation comes with it automatically. The Pro repo URL is provided after purchase — contact [nerdsnipe.inc@gmail.com](mailto:nerdsnipe.inc@gmail.com) or visit the [Pro page](https://nerdsnipe-inc.github.io/design-foundation/pro/) to get access.\n\n```\ndependencies: [\n    .package(url: \"https://github.com/NerdSnipe-Inc/DesignFoundationPro\", from: \"1.0.0\"),\n],\ntargets: [\n    .target(\n        name: \"YourApp\",\n        dependencies: [\n            .product(name: \"DesignFoundationPro\", package: \"DesignFoundationPro\"),\n        ]\n    ),\n]\n```\n\nOne package, one import: `import DesignFoundationPro`\n\ngives you both layers with no separate Foundation entry needed.\n\nPro adds `DFTheme.workspace`\n\n— a platform-adaptive preset tuned for data-dense product UIs. It uses tighter desktop typography and adjusted surface tokens on macOS. For most product apps, this is the right starting point.\n\n``` python\nimport SwiftUI\nimport DesignFoundationPro\n\n@main\nstruct MyApp: App {\n    var body: some Scene {\n        WindowGroup {\n            MyRootView()\n                .dfTheme(.workspace)\n        }\n    }\n}\n```\n\nStill want a custom brand?Build your`DFTheme`\n\nfrom tokens as shown in Part 1 — the individual blocks (auth, charts, onboarding) genuinely read whatever theme is in the environment. The composition roots and shells in Step 8 are the exception: each one hard-codes`.environment(\\.dfTheme, .workspace)`\n\ninternally, so mounting`DFCRMRootView()`\n\n(or any other root, or`DFOnboardingFlow`\n\n) always renders in`.workspace`\n\nregardless of what theme you set upstream. If you need a branded CRM/PM/Analytics root, that's a real customization, not a one-line override — check the root's source for where it applies the theme before assuming an ambient override will reach it.\n\nEvery product app needs some variation of \"sidebar on Mac and iPad, tabs on iPhone,\" and it's one of the most-rebuilt pieces of chrome in SwiftUI — split view state, collapse behavior, selection binding, all rewritten per project. Shells are layout-only, pre-solved versions of that problem. They give you navigation structure — sidebar, columns, tabs — and expose `@ViewBuilder`\n\nslots for your own content. No data or business logic.\n\n| Shell | Use for |\n|---|---|\n`DFStandardSidebarShell` |\nSingle sidebar + detail. macOS-native split view. |\n`DFThreeColumnShell` |\nSource list → content → inspector. Documents, mail. |\n`DFWorkspaceSidebarShell` |\nCollapsible sidebar with workspace density tokens. |\n`DFIconRailShell` |\nNarrow icon rail. Dashboard or tool-focused apps. |\n`DFAdaptiveShell` |\nTabs on iPhone, sidebar on iPad and Mac. |\n`DFSearchSidebarShell` |\nSidebar with integrated search bar at top. |\n\nEach shell has its own init signature matching its structural role. (The table above lists 6 of the 18 general-purpose shells; the rest follow the same pattern.) There's also a 19th, vertical-specific shell — `DFSocialAppShell`\n\n— built for feed/explore/notifications/profile apps specifically rather than generic navigation. It's the simplest to read, with four named `@ViewBuilder`\n\nslots:\n\n``` js\nstruct MyRootView: View {\n    var body: some View {\n        DFSocialAppShell {\n            FeedView()\n        } explore: {\n            ExploreView()\n        } notifications: {\n            NotificationsView()\n        } profile: {\n            ProfileView()\n        }\n        .dfToastHost()\n    }\n}\n```\n\nThe sidebar shells take typed nav data alongside a selection binding and a content `@ViewBuilder`\n\n: `DFStandardSidebarShell`\n\ntakes `sections: [DFNavSection]`\n\n(each section holds `[DFNavItem]`\n\n), `DFAdaptiveShell`\n\ntakes `[DFAdaptiveTab]`\n\n, `DFWorkspaceSidebarShell`\n\ntakes `[DFWorkspace]`\n\nplus an icon rail and account switcher. The composition roots (Step 8) wire all of this for you when you're building a known vertical.\n\nMountAfter that, trigger toasts from anywhere in the tree with`.dfToastHost()`\n\nonce on any root view.`DFToastQueue.shared.show(text: \"Saved\", severity: .success)`\n\n.\n\nAuth screens feel simple until you're deep into forgot-password states, social provider buttons, OTP resend timers, and inline validation — a week of work for something that isn't your product's differentiator.\n\nThe four Pro auth blocks — `DFWelcomeBlock`\n\n, `DFSignInBlock`\n\n, `DFSignUpBlock`\n\n, `DFOTPBlock`\n\n— each take a `Configuration`\n\nstruct with callbacks. Form validation is wired by default using `DFFormState`\n\nfrom Foundation; set `usesFormValidation: false`\n\nonly when you own it yourself.\n\n```\nDFSignInBlock(configuration: .init(\n    title: \"Sign in to Acme\",\n    subtitle: \"Welcome back\",\n    socialProviders: [\n        .apple  { Task { await AuthService.signInWithApple()  } },\n        .google { Task { await AuthService.signInWithGoogle() } },\n    ],\n    onSubmit: { email, password in\n        // onSubmit is not async — wrap async work in a Task\n        Task { await AuthService.signIn(email: email, password: password) }\n    },\n    onForgotPassword: { showForgotPassword() },\n    onSignUp: { showSignUp() }\n))\n```\n\nThe block handles layout, field spacing, validation error display, and social auth button presentation across iOS, macOS, and visionOS. Desktop density is applied automatically from `@Environment(\\.dfUseDesktopDensity)`\n\n.\n\nPair `DFSignUpBlock`\n\nwith `DFOTPBlock`\n\nfor email verification. Each block is independent — you control navigation between them:\n\n```\nDFOTPBlock(configuration: .init(\n    title: \"Check your email\",\n    subtitle: registeredEmail,   // shown under the title\n    onSubmit: { code in\n        // onSubmit is not async — wrap async work in a Task\n        Task { await AuthService.verify(otp: code) }\n    },\n    onResend: {\n        Task { await AuthService.resendOTP() }\n    }\n))\n```\n\nOnboarding flows are where a lot of hand-rolled apps quietly go wrong — the \"what screen comes next\" logic ends up spread across seven view files with their own local state, and resuming mid-flow after the app is backgrounded is an afterthought nobody tests.\n\n`DFOnboardingFlow`\n\nis the highest-level Pro component: a multi-step flow that covers welcome, sign-up or sign-in, OTP verification, profile setup, permission requests, plan selection, personalisation, and a success screen. It manages its own step state (persisted across launches via `UserDefaults`\n\n) and handles back navigation.\n\nYou configure the whole flow via `DFOnboardingConfiguration`\n\n— one struct, no subclassing:\n\n```\nDFOnboardingFlow(configuration: .init(\n    appName:         \"Acme\",\n    welcomeTagline:  \"Welcome to Acme\",\n    welcomeHeadline: \"The best tool for your team.\",\n    enableAppleSignIn:  true,\n    enableGoogleSignIn: true,\n\n    featureHighlights: [\n        .init(icon: \"sparkles\", title: \"AI-Powered\",\n              description: \"Smart suggestions across every workflow.\"),\n        .init(icon: \"person.3\", title: \"Built for Teams\",\n              description: \"Real-time collaboration, no friction.\"),\n    ],\n\n    requestedPermissions: [.notifications],\n\n    showPlanSelection: true,\n    planTiers: [\n        .init(name: \"Free\",  price: \"$0\",   period: \"forever\",\n              features: [\"3 projects\", \"1,000 API calls\"], isPopular: false),\n        .init(name: \"Pro\",   price: \"$49\",  period: \"/ month\",\n              features: [\"Unlimited projects\", \"Priority support\"],\n              isPopular: true, badge: \"Most Popular\"),\n    ],\n\n    personalisationTags: [\n        .init(id: \"startup\",    label: \"Startup\"),\n        .init(id: \"enterprise\", label: \"Enterprise\"),\n        .init(id: \"freelance\",  label: \"Freelance\"),\n    ],\n\n    onSignUp:  { name, email, password in\n        // Return Bool: true = success, false = failure (e.g. email taken)\n        return await AuthService.signUp(name: name, email: email, password: password)\n    },\n    onSignIn:  { email, password in\n        return await AuthService.signIn(email: email, password: password)\n    },\n    onOTPVerify:             { code in return await AuthService.verify(otp: code) },\n    onOTPResend:             {           await AuthService.resendOTP()         },\n    onProfileSave:           { name, avatar in await ProfileService.save(name, avatar) },\n    onPermissionRequest:     { perm in  await PermissionService.request(perm) },\n    onPlanSelected:          { tier in   BillingService.select(tier)          },\n    onPersonalisationComplete: { tags in ProfileService.saveTags(tags)        },\n    onComplete:              {           AppState.shared.isOnboarded = true   }\n))\n```\n\nThat one initialiser configures every step. If the user backgrounds the app mid-onboarding, they resume where they left off.\n\nSwift Charts gives you the primitives, but every dashboard still needs the same surrounding scaffolding — a period picker, a legend, loading and empty states, consistent theme colors per series. That's the part that gets rebuilt per project.\n\nChart blocks are composables, not screen-locked. Drop them into your own views, not just the Analytics root. They use Swift Charts under the hood and inherit theme colors automatically.\n\n| Block | Use for |\n|---|---|\n`DFLineChartBlock` |\nTrends, time series, cumulative metrics |\n`DFBarChartBlock` |\nCategory comparison, period-over-period |\n`DFDonutChartBlock` |\nPart-to-whole breakdown, proportions |\n`DFStatCardBlock` |\nSingle metric with delta and trend sparkline |\n\nDon't useIt's a loading/skeleton state only. For loading chart data, pass`DFChartPlaceholderBlock`\n\nin shipping UI.`isLoading: true`\n\nto the real chart block instead.\n\n```\n// Keyword arguments must appear in the same order as the initializer declares them —\n// isLoading: comes right after title:, before data:.\nDFLineChartBlock(configuration: .init(\n    title: \"Active Users\",\n    isLoading: isFetchingData,\n    data: [\n        (\"Mon\", 182), (\"Tue\", 195), (\"Wed\", 188),\n        (\"Thu\", 212), (\"Fri\", 240), (\"Sat\", 198),\n    ],\n    periods: DFChartPeriodPreset.allCases,\n    selectedPeriod: .sevenDays,\n    onPeriodChange: { period in\n        // onPeriodChange is not async — wrap async work in a Task\n        Task { await loadTrend(for: period) }\n    },\n    height: isDesktop ? 140 : 220\n))\n```\n\nThe period toolbar is built in. Your domain period enum conforms to `DFChartPeriodRepresentable`\n\nif you need custom ranges beyond the preset.\n\nA data table that feels native on macOS (keyboard navigation, `Return`\n\nto activate a row) and still works on a phone-width screen is two different UIs wearing the same data. Building both, and deciding which one renders where, is a recurring cross-platform tax.\n\n`DFDataTable`\n\nships in Foundation but it's most useful in Pro-scale apps. On macOS it renders a native `Table`\n\nwith keyboard navigation and Return-to-activate. On iPhone it falls back to scrollable header + rows. Use it on regular-width layouts; keep `DFList`\n\nwith swipe actions on compact phone screens where that fits better.\n\n``` js\n@State private var selectedIDs: Set<Contact.ID> = []\n\nprivate var columns: [DFDataTableColumn<Contact>] {\n    [\n        DFDataTableColumn(id: \"name\",    title: \"Name\")    { $0.name            },\n        DFDataTableColumn(id: \"company\", title: \"Company\") { $0.company         },\n        DFDataTableColumn(id: \"status\",  title: \"Status\")  { $0.status.rawValue },\n    ]\n}\n\nDFDataTable(\n    data:          filteredContacts,\n    columns:       columns,\n    selection:     $selectedIDs,\n    selectionMode: .multiple,\n    filterQuery:   searchText,\n    onRowActivate: { openDetail($0) },\n    emptyContent: {\n        DFEmptyStateBlock(configuration: .init(\n            icon:    \"person.crop.circle.badge.questionmark\",\n            title:   \"No contacts found\",\n            message: \"Try a different search or filter.\"\n        ))\n    }\n)\n```\n\nStarting a new product vertical from a blank `ContentView`\n\nmeans weeks before it looks like a real app. If you're building a known vertical, Pro ships full composition roots instead: pre-wired screens, navigation, and blocks. Mount the root, replace the fixture data with your models, and you have something that looks like a real product immediately.\n\n| Root | What it includes |\n|---|---|\n`DFCRMRootView` |\nHome, pipeline, contacts table, analytics |\n`DFPMRootView` |\nProject board, list, timeline, team roster |\n`DFAnalyticsRootView` |\nOverview, events, revenue, users — all with charts |\n`DFAIChatRootView` |\nNew chat, thread, compare view, settings sheet |\n`DFEcommerceRootView` |\nStore home, orders table, products, revenue |\n`DFSettingsRootView` |\nAccount, security, billing, team, danger zone |\n`DFSocialAppShell` |\nFeed, explore, notifications, profile |\n\n``` js\nstruct MyRootView: View {\n    var body: some View {\n        DFCRMRootView()\n            .dfToastHost()\n    }\n}\n```\n\nThe `.environment(\\.dfTheme, .workspace)`\n\nline from Step 2 isn't needed here — every composition root applies `.workspace`\n\ninternally, so it renders themed correctly with no setup at all. The root ships with preview fixtures (`CRMPreviewFixtures`\n\n, etc.) so it renders immediately with realistic data. Replace those fixtures with your own domain types as you go. Keep the layout, the density helpers, and the theme wiring — those are the parts you don't want to rebuild.\n\nQuality gate.Before shipping a screen built on Pro,`QUALITY-GATE.md`\n\nin the repo has 15 review items including platform-native layout, non-happy-path previews, and correct root wiring. The bar is: would this hold up as a standalone app?\n\nEvery form in an app tends to reinvent its own field-to-validator plumbing — a slightly different `isValid`\n\ncomputed property each time. Pro auth and settings screens wire `DFFormState`\n\nby default so that plumbing exists once. If you need the same pattern in your own views:\n\n``` js\nprivate enum Field {\n    static let email    = \"email\"\n    static let password = \"password\"\n}\n\n@State private var form = DFFormState()\n\n// Register fields and validators — e.g. in .task or a setup method:\nform.register(field: Field.email,    validators: [DFRequiredValidator(), DFEmailValidator()])\nform.register(field: Field.password, validators: [DFRequiredValidator(), DFMinLengthValidator(minLength: 8)])\n\n// In your view body:\nDFValidatedTextField(\"Email\", field: Field.email, form: form)\n\nDFSecureField(\n    \"Password\",\n    text: form.binding(for: Field.password),\n    validationState: form.validationState(for: Field.password)\n)\n\nDFButton(\"Sign in\") {\n    guard form.validate() else { return }\n    // DFButton action is () -> Void — wrap async work in a Task\n    Task {\n        await submit(\n            form.values[Field.email,    default: \"\"],\n            form.values[Field.password, default: \"\"]\n        )\n    }\n}\n```\n\nBuilt-in validators: `DFRequiredValidator`\n\n, `DFEmailValidator`\n\n, `DFMinLengthValidator`\n\n, `DFMaxLengthValidator`\n\n, `DFRegexValidator`\n\n. Conform to `DFFieldValidator`\n\nto add your own.\n\nPro ships the same `CLAUDE.md`\n\n/ `AGENTS.md`\n\n/ `.cursor`\n\nrule file as Foundation, plus more: a `.cursor/skills`\n\nand `.cursor/references`\n\nlibrary covering Apple's Human Interface Guidelines platform-by-platform, Liquid Glass, Swift Charts patterns, accessibility, and desktop-app archetypes. Point Claude Code, Cursor, or Codex at a repo that depends on DesignFoundationPro and it isn't just told \"use `DFButton`\n\n\" — it has design-pattern reference material to draw on when it's deciding how a screen should actually look.\n\nThat's the difference between an agent that produces a working screen and one that produces a *correct-looking, cross-platform* screen without a single `#if os(macOS)`\n\nblock — because the platform branching already happened inside the shell, the block, or the root it reached for. The practical result: when three different agent sessions build three different features of the same app, they come out looking like one product instead of three.\n\nDesignFoundationPro is designed to be adopted incrementally — a single block takes about 5 minutes to drop in, a full screen 10 minutes, a composition root 15. You don't need the full vertical to get value from it.\n\nIssues and feedback on the [GitHub repo](https://github.com/NerdSnipe-Inc/DesignFoundationPro). If you missed the foundation layer, start with [Part 1](https://dev.to/nerdsnipe/stop-rebuilding-the-same-swiftui-components-a-guide-to-designfoundation).", "url": "https://wpnews.pro/news/stop-rebuilding-auth-onboarding-and-dashboards-designfoundationpro", "canonical_source": "https://dev.to/nerd_snipe_dev/stop-rebuilding-auth-onboarding-and-dashboards-designfoundationpro-39oj", "published_at": "2026-07-07 06:26:00+00:00", "updated_at": "2026-07-07 06:28:18.473131+00:00", "lang": "en", "topics": ["developer-tools", "ai-agents", "generative-ai"], "entities": ["NerdSnipe Inc.", "DesignFoundationPro", "DesignFoundation", "SwiftUI", "DFPlayground", "macOS", "AI coding agent"], "alternates": {"html": "https://wpnews.pro/news/stop-rebuilding-auth-onboarding-and-dashboards-designfoundationpro", "markdown": "https://wpnews.pro/news/stop-rebuilding-auth-onboarding-and-dashboards-designfoundationpro.md", "text": "https://wpnews.pro/news/stop-rebuilding-auth-onboarding-and-dashboards-designfoundationpro.txt", "jsonld": "https://wpnews.pro/news/stop-rebuilding-auth-onboarding-and-dashboards-designfoundationpro.jsonld"}}