cd /news/developer-tools/stop-rebuilding-auth-onboarding-and-… Β· home β€Ί topics β€Ί developer-tools β€Ί article
[ARTICLE Β· art-49022] src=dev.to β†— pub= topic=developer-tools verified=true sentiment=↑ positive

Stop Rebuilding Auth, Onboarding, and Dashboards: DesignFoundationPro

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.

read12 min views1 publishedJul 7, 2026

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.

The 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.

This 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.

Pro sits on top of Foundation and re-exports it. Import only DesignFoundationPro

and you get everything from both:

YourApp                    your models, data, routing
DesignFoundationPro        29 blocks Β· 47 screens Β· 18 shells Β· 9 examples
DesignFoundation           tokens Β· primitives Β· validation Β· theme engine Β· MIT

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).

Before buying, browse everything in DFPlayground β€” a free macOS app that lets you preview all 29 blocks, 47 screens, and 18 shells with live theming.

Pro declares Foundation as its own dependency and re-exports it via @_exported import DesignFoundation

. Add only the Pro package β€” Foundation comes with it automatically. The Pro repo URL is provided after purchase β€” contact nerdsnipe.inc@gmail.com or visit the Pro page to get access.

dependencies: [
    .package(url: "https://github.com/NerdSnipe-Inc/DesignFoundationPro", from: "1.0.0"),
],
targets: [
    .target(
        name: "YourApp",
        dependencies: [
            .product(name: "DesignFoundationPro", package: "DesignFoundationPro"),
        ]
    ),
]

One package, one import: import DesignFoundationPro

gives you both layers with no separate Foundation entry needed.

Pro adds DFTheme.workspace

β€” 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.

import SwiftUI
import DesignFoundationPro

@main
struct MyApp: App {
    var body: some Scene {
        WindowGroup {
            MyRootView()
                .dfTheme(.workspace)
        }
    }
}

Still want a custom brand?Build yourDFTheme

from 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)

internally, so mountingDFCRMRootView()

(or any other root, orDFOnboardingFlow

) always renders in.workspace

regardless 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.

Every 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

slots for your own content. No data or business logic.

Shell Use for
DFStandardSidebarShell
Single sidebar + detail. macOS-native split view.
DFThreeColumnShell
Source list β†’ content β†’ inspector. Documents, mail.
DFWorkspaceSidebarShell
Collapsible sidebar with workspace density tokens.
DFIconRailShell
Narrow icon rail. Dashboard or tool-focused apps.
DFAdaptiveShell
Tabs on iPhone, sidebar on iPad and Mac.
DFSearchSidebarShell
Sidebar with integrated search bar at top.

Each 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

β€” built for feed/explore/notifications/profile apps specifically rather than generic navigation. It's the simplest to read, with four named @ViewBuilder

slots:

struct MyRootView: View {
    var body: some View {
        DFSocialAppShell {
            FeedView()
        } explore: {
            ExploreView()
        } notifications: {
            NotificationsView()
        } profile: {
            ProfileView()
        }
        .dfToastHost()
    }
}

The sidebar shells take typed nav data alongside a selection binding and a content @ViewBuilder

: DFStandardSidebarShell

takes sections: [DFNavSection]

(each section holds [DFNavItem]

), DFAdaptiveShell

takes [DFAdaptiveTab]

, DFWorkspaceSidebarShell

takes [DFWorkspace]

plus an icon rail and account switcher. The composition roots (Step 8) wire all of this for you when you're building a known vertical.

MountAfter that, trigger toasts from anywhere in the tree with.dfToastHost()

once on any root view.DFToastQueue.shared.show(text: "Saved", severity: .success)

.

Auth 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.

The four Pro auth blocks β€” DFWelcomeBlock

, DFSignInBlock

, DFSignUpBlock

, DFOTPBlock

β€” each take a Configuration

struct with callbacks. Form validation is wired by default using DFFormState

from Foundation; set usesFormValidation: false

only when you own it yourself.

DFSignInBlock(configuration: .init(
    title: "Sign in to Acme",
    subtitle: "Welcome back",
    socialProviders: [
        .apple  { Task { await AuthService.signInWithApple()  } },
        .google { Task { await AuthService.signInWithGoogle() } },
    ],
    onSubmit: { email, password in
        // onSubmit is not async β€” wrap async work in a Task
        Task { await AuthService.signIn(email: email, password: password) }
    },
    onForgotPassword: { showForgotPassword() },
    onSignUp: { showSignUp() }
))

The 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)

.

Pair DFSignUpBlock

with DFOTPBlock

for email verification. Each block is independent β€” you control navigation between them:

DFOTPBlock(configuration: .init(
    title: "Check your email",
    subtitle: registeredEmail,   // shown under the title
    onSubmit: { code in
        // onSubmit is not async β€” wrap async work in a Task
        Task { await AuthService.verify(otp: code) }
    },
    onResend: {
        Task { await AuthService.resendOTP() }
    }
))

Onboarding 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.

DFOnboardingFlow

is 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

) and handles back navigation.

You configure the whole flow via DFOnboardingConfiguration

β€” one struct, no subclassing:

DFOnboardingFlow(configuration: .init(
    appName:         "Acme",
    welcomeTagline:  "Welcome to Acme",
    welcomeHeadline: "The best tool for your team.",
    enableAppleSignIn:  true,
    enableGoogleSignIn: true,

    featureHighlights: [
        .init(icon: "sparkles", title: "AI-Powered",
              description: "Smart suggestions across every workflow."),
        .init(icon: "person.3", title: "Built for Teams",
              description: "Real-time collaboration, no friction."),
    ],

    requestedPermissions: [.notifications],

    showPlanSelection: true,
    planTiers: [
        .init(name: "Free",  price: "$0",   period: "forever",
              features: ["3 projects", "1,000 API calls"], isPopular: false),
        .init(name: "Pro",   price: "$49",  period: "/ month",
              features: ["Unlimited projects", "Priority support"],
              isPopular: true, badge: "Most Popular"),
    ],

    personalisationTags: [
        .init(id: "startup",    label: "Startup"),
        .init(id: "enterprise", label: "Enterprise"),
        .init(id: "freelance",  label: "Freelance"),
    ],

    onSignUp:  { name, email, password in
        // Return Bool: true = success, false = failure (e.g. email taken)
        return await AuthService.signUp(name: name, email: email, password: password)
    },
    onSignIn:  { email, password in
        return await AuthService.signIn(email: email, password: password)
    },
    onOTPVerify:             { code in return await AuthService.verify(otp: code) },
    onOTPResend:             {           await AuthService.resendOTP()         },
    onProfileSave:           { name, avatar in await ProfileService.save(name, avatar) },
    onPermissionRequest:     { perm in  await PermissionService.request(perm) },
    onPlanSelected:          { tier in   BillingService.select(tier)          },
    onPersonalisationComplete: { tags in ProfileService.saveTags(tags)        },
    onComplete:              {           AppState.shared.isOnboarded = true   }
))

That one initialiser configures every step. If the user backgrounds the app mid-onboarding, they resume where they left off.

Swift Charts gives you the primitives, but every dashboard still needs the same surrounding scaffolding β€” a period picker, a legend, and empty states, consistent theme colors per series. That's the part that gets rebuilt per project.

Chart 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.

Block Use for
DFLineChartBlock
Trends, time series, cumulative metrics
DFBarChartBlock
Category comparison, period-over-period
DFDonutChartBlock
Part-to-whole breakdown, proportions
DFStatCardBlock
Single metric with delta and trend sparkline

Don't useIt's a /skeleton state only. For chart data, passDFChartPlaceholderBlock

in shipping UI.is: true

to the real chart block instead.

// Keyword arguments must appear in the same order as the initializer declares them β€”
// is: comes right after title:, before data:.
DFLineChartBlock(configuration: .init(
    title: "Active Users",
    is: isFetchingData,
    data: [
        ("Mon", 182), ("Tue", 195), ("Wed", 188),
        ("Thu", 212), ("Fri", 240), ("Sat", 198),
    ],
    periods: DFChartPeriodPreset.allCases,
    selectedPeriod: .sevenDays,
    onPeriodChange: { period in
        // onPeriodChange is not async β€” wrap async work in a Task
        Task { await loadTrend(for: period) }
    },
    height: isDesktop ? 140 : 220
))

The period toolbar is built in. Your domain period enum conforms to DFChartPeriodRepresentable

if you need custom ranges beyond the preset.

A data table that feels native on macOS (keyboard navigation, Return

to 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.

DFDataTable

ships in Foundation but it's most useful in Pro-scale apps. On macOS it renders a native Table

with keyboard navigation and Return-to-activate. On iPhone it falls back to scrollable header + rows. Use it on regular-width layouts; keep DFList

with swipe actions on compact phone screens where that fits better.

@State private var selectedIDs: Set<Contact.ID> = []

private var columns: [DFDataTableColumn<Contact>] {
    [
        DFDataTableColumn(id: "name",    title: "Name")    { $0.name            },
        DFDataTableColumn(id: "company", title: "Company") { $0.company         },
        DFDataTableColumn(id: "status",  title: "Status")  { $0.status.rawValue },
    ]
}

DFDataTable(
    data:          filteredContacts,
    columns:       columns,
    selection:     $selectedIDs,
    selectionMode: .multiple,
    filterQuery:   searchText,
    onRowActivate: { openDetail($0) },
    emptyContent: {
        DFEmptyStateBlock(configuration: .init(
            icon:    "person.crop.circle.badge.questionmark",
            title:   "No contacts found",
            message: "Try a different search or filter."
        ))
    }
)

Starting a new product vertical from a blank ContentView

means 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.

Root What it includes
DFCRMRootView
Home, pipeline, contacts table, analytics
DFPMRootView
Project board, list, timeline, team roster
DFAnalyticsRootView
Overview, events, revenue, users β€” all with charts
DFAIChatRootView
New chat, thread, compare view, settings sheet
DFEcommerceRootView
Store home, orders table, products, revenue
DFSettingsRootView
Account, security, billing, team, danger zone
DFSocialAppShell
Feed, explore, notifications, profile
struct MyRootView: View {
    var body: some View {
        DFCRMRootView()
            .dfToastHost()
    }
}

The .environment(\.dfTheme, .workspace)

line from Step 2 isn't needed here β€” every composition root applies .workspace

internally, so it renders themed correctly with no setup at all. The root ships with preview fixtures (CRMPreviewFixtures

, 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.

Quality gate.Before shipping a screen built on Pro,QUALITY-GATE.md

in 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?

Every form in an app tends to reinvent its own field-to-validator plumbing β€” a slightly different isValid

computed property each time. Pro auth and settings screens wire DFFormState

by default so that plumbing exists once. If you need the same pattern in your own views:

private enum Field {
    static let email    = "email"
    static let password = "password"
}

@State private var form = DFFormState()

// Register fields and validators β€” e.g. in .task or a setup method:
form.register(field: Field.email,    validators: [DFRequiredValidator(), DFEmailValidator()])
form.register(field: Field.password, validators: [DFRequiredValidator(), DFMinLengthValidator(minLength: 8)])

// In your view body:
DFValidatedTextField("Email", field: Field.email, form: form)

DFSecureField(
    "Password",
    text: form.binding(for: Field.password),
    validationState: form.validationState(for: Field.password)
)

DFButton("Sign in") {
    guard form.validate() else { return }
    // DFButton action is () -> Void β€” wrap async work in a Task
    Task {
        await submit(
            form.values[Field.email,    default: ""],
            form.values[Field.password, default: ""]
        )
    }
}

Built-in validators: DFRequiredValidator

, DFEmailValidator

, DFMinLengthValidator

, DFMaxLengthValidator

, DFRegexValidator

. Conform to DFFieldValidator

to add your own.

Pro ships the same CLAUDE.md

/ AGENTS.md

/ .cursor

rule file as Foundation, plus more: a .cursor/skills

and .cursor/references

library 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

" β€” it has design-pattern reference material to draw on when it's deciding how a screen should actually look.

That'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)

block β€” 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.

DesignFoundationPro 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.

Issues and feedback on the GitHub repo. If you missed the foundation layer, start with Part 1.

── more in #developer-tools 4 stories Β· sorted by recency
── more on @nerdsnipe inc. 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain β€” perfect for shipping the agent you just read about.

$git push zahid main
β†’ Live at https://your-agent.zahid.host βœ“
Get free account β†’ Pricing
from €0/mo Β· no card required
LIVE [news/stop-rebuilding-auth…] indexed:0 read:12min 2026-07-07 Β· β€”