Getting Started

Spring Design Library — Developer Guide

A complete reference for developers, contributors, and teams building with or on top of the Spring Design System. This guide covers how the library is architected, how to use it, how to extend it, and how to evaluate it for production at scale.


Table of Contents

  1. What Is This Library?
  2. Entry Points — How to Consume the Library
  3. How to Use Components
  4. React and Next.js Guidelines for New Users
  5. Why the Structure Works This Way
  6. How to Add Your Own Component
  7. How Production Builds Work
  8. The Agentic Workflow
  9. Building a Full Complex Repo With This Component Set
  10. What to Do and What Not to Do
  11. Production Readiness Evaluation
  12. Scalability Reflection

1. What Is This Library?

The Spring Design Library is a monorepo design system built by the Opportunity Generation team at Spring Financial. It ships:

  • A published React component packagespring-design-library-react (used internally as @spring/ds-react) — containing tokens, components, icons, design rules, and the AppFlow page registry.
  • A Next.js documentation gallery@spring/design-library — running at / (Getting Started), with top-level routes like /components/*, /design-tokens/*, and /page/*. This replaces Storybook as the visual source of truth.
  • An AppFlow package@spring/personal-loan-appflow — containing the 24-step personal loan application flow.
  • A standalone personal-loan app@spring/personal-loan — a production-style Next.js app that consumes the AppFlow package.

Stack: Next.js 16 (App Router), React 19, TypeScript, Tailwind CSS 3.4
Workspace manager: npm workspaces (no Turborepo, no pnpm)
Registry: Gemfury (npm.fury.io/springfinancial/)

spring-design-library/            ← npm workspace root
├── packages/
│   ├── spring-ds-react/          ← published package (@spring/ds-react)
│   └── personal-loan-appflow/    ← AppFlow logic (@spring/personal-loan-appflow)
├── apps/
│   ├── design-library/           ← documentation gallery (@spring/design-library)
│   └── personal-loan/            ← standalone flow app (@spring/personal-loan)
├── scripts/
│   └── sync-tokens.js            ← token build + Figma pull
└── docs/                         ← documentation hub (this file lives here)

2. Entry Points — How to Consume the Library

The published package spring-design-library-react exposes six distinct subpath exports. Each serves a different consumer need.

2.1 Root export — all components

import { SpringButton, TextField, SpringHeader } from 'spring-design-library-react'
// or from the internal alias:
import { SpringButton, TextField, SpringHeader } from '@spring/ds-react'

This is the primary entry point. Every implemented component is available here.

2.2 /tokens — framework-agnostic design tokens

import {
  tokens,
  getToken,
  getByPath,
  figmaNameToCssVar,
  AI_REFERENCE_MAP,
} from 'spring-design-library-react/tokens'

Use when you need to read token values in JavaScript (e.g. building a Tailwind theme, a chart palette, or an AI reference map). This is ESM-only.

2.3 /tokens/generated/css-variables.css — 254 CSS custom properties

// In Next.js root layout (SSR-safe):
import 'spring-design-library-react/tokens/generated/css-variables.css'

This single import makes every --spring-* CSS variable available globally. Always import this in your root layout before any component styles.

2.4 /icons — ~1,173 Figma-synced icon components

import { IconArrowRight, IconCheckCircle } from 'spring-design-library-react/icons'

<IconArrowRight className="w-5 h-5 text-spring-color-plum-600" />

Icons are organized into 20 categories (General, Arrows, Charts, Communication, Finance, etc.) and auto-synced from Figma.

2.5 /rules — structured design-system rules

import { rules } from 'spring-design-library-react/rules'

Machine-readable design rules. Primarily consumed by AI agents and linters.

2.6 /appflow — personal loan flow registry

import {
  PERSONAL_LOAN_STEPS,
  AppFlowShell,
  PersonalLoanFlowChart,
} from 'spring-design-library-react/appflow'

Bundles @spring/personal-loan-appflow. Use when integrating the full 24-step personal loan flow into a product app.

2.7 Summary table

SubpathWhat you getWhen to use
root / /componentsAll UI componentsEvery project
/tokensToken values as JS objectsTailwind config, charts, AI reference
/tokens/generated/css-variables.cssCSS custom propertiesRoot layout import
/icons1,173 SVG icon componentsAnywhere you need iconography
/rulesDesign system rulesAI tooling, lint rules
/appflowPersonal loan flow + registryProduct apps integrating the flow

3. How to Use Components

3.1 Installation (consuming repo)

npm install spring-design-library-react
# or pin to internal registry:
npm install spring-design-library-react --registry https://npm.fury.io/springfinancial/

3.2 Wire up tokens (do this once)

In your Next.js root layout (app/layout.tsx):

import 'spring-design-library-react/tokens/generated/css-variables.css'
import './globals.css'  // your Tailwind base

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  )
}

In tailwind.config.mjs, extend the theme with Spring tokens:

import { tokens, figmaNameToCssVar } from 'spring-design-library-react/tokens'

export default {
  content: ['./src/**/*.{ts,tsx}', './node_modules/spring-design-library-react/src/**/*.{ts,tsx}'],
  theme: {
    extend: {
      colors: {
        // Map semantic token CSS vars to Tailwind color utilities
        background: 'hsl(var(--spring-semantic-color-bg-canvas) / <alpha-value>)',
        foreground: 'hsl(var(--spring-semantic-color-text-primary) / <alpha-value>)',
        // ... (see apps/design-library/tailwind.config.mjs for the full mapping)
      },
    },
  },
}

Note: The design library's own tailwind.config.mjs is the reference implementation. Copy its pattern when setting up a consuming app.

3.3 Using a component

All components follow the same import pattern:

import { SpringButton } from '@spring/ds-react'

export default function MyPage() {
  return (
    <SpringButton variant="primary" size="lg" onClick={() => console.log('clicked')}>
      Get Started
    </SpringButton>
  )
}

3.4 Component catalogue

CategoryComponents
actionsSpringButton
navigationsSpringHeader, SpringFooter, SpringFooterLegal, SpringLogo
selection-inputSpringSearchBar
overlaysExitModal, InfoModal, ConfirmModal, ActionModal
feedbackSpringProgress
inputsTextField, DropdownField, PhoneNumberField, SplitPillInput, OtpField, SearchField, BoxSelector, LoanAmountSlider, TileSelector, ConsentBox, FileUploader
modulesAddressFields, FaqAccordion, PlanCard, PlanSummaryCard, SpringLoanCard

3.5 Props discovery

Every component is documented in the design gallery at /components/<category>/<component-name>. Each page includes:

  • Live playground with variant/size controls
  • Props table with type, default, and description
  • States grid — default, active, error, disabled, loading
  • Code block — copy-paste ready snippet
  • Figma link to the source design

3.6 Key component patterns

SpringButton — renders as <button> or <a> based on whether href is set:

// Button
<SpringButton variant="primary" size="lg">Apply Now</SpringButton>

// Link
<SpringButton variant="outlined" size="md" href="/learn-more">
  Learn More
</SpringButton>

// Loading state
<SpringButton variant="primary" loading>Processing...</SpringButton>

// With icons
<SpringButton variant="primary" leadIcon={<IconArrowRight />}>Next Step</SpringButton>

TextField — floating label, status-aware:

<TextField
  label="First Name"
  value={value}
  onChange={(e) => setValue(e.target.value)}
  status="default"          // 'default' | 'active' | 'error'
  helpMessage="As on your ID"
  errorMessage="Required"
/>

AppFlow screens — presentational-only, no data fetching:

// Screens receive only display-layer props; data binding happens outside the gallery
<AppFlowNameScreen
  onNext={() => router.push('/loan-amount')}
  onBack={() => router.back()}
/>

4. React and Next.js Guidelines for New Users

4.1 Server Components vs. Client Components

This library follows Next.js App Router conventions. Default to Server Components.

// Server Component (default) — no 'use client' needed
// Good for: layouts, wrappers, static content
export default function LoanSummarySection() {
  return (
    <section>
      <PlanSummaryCard title="Personal Loan" amount="$15,000" rate="9.99%" />
    </section>
  )
}
'use client'
// Client Component — required when using:
// - React hooks (useState, useEffect, useRef)
// - Browser APIs (window, localStorage)
// - Event handlers on interactive elements
// - Context consumers

export default function LoanAmountInput() {
  const [amount, setAmount] = React.useState(5000)
  return <LoanAmountSlider value={amount} onChange={setAmount} />
}

Rule: Add 'use client' only when the component needs hooks, events, lifecycle, or browser APIs. Spring components that are interactive (inputs, modals, search) are already 'use client' internally — their parent can still be a Server Component.

4.2 Styling: always use tokens

Never write raw CSS values or arbitrary Tailwind values:

// ✅ Correct — semantic token class
<div className="bg-background text-foreground p-space-4">

// ✅ Correct — CSS variable reference
<div style={{ color: 'var(--spring-semantic-color-text-primary)' }}>

// ❌ Wrong — raw hex
<div style={{ color: '#1A1A2E' }}>

// ❌ Wrong — arbitrary Tailwind value
<div className="bg-[#1A1A2E]">

4.3 Route structure

Follow the thin-shell pattern:

apps/your-app/src/
├── app/
│   └── loans/
│       └── page.tsx          ← thin shell, one-liner
└── pages/
    └── LoansPage.tsx         ← all rendering logic here
// apps/your-app/src/app/loans/page.tsx
import LoansPage from '@/pages/LoansPage'
export default LoansPage

This keeps Next.js route files trivially replaceable and all logic testable independently.

4.4 Import paths

Always import components from the package, not from internal implementation paths:

// ✅ Correct — public package import
import { SpringButton, TextField } from '@spring/ds-react'

// ❌ Wrong — reaching into package internals
import SpringButton from '../../packages/spring-ds-react/src/components/actions/SpringButton'

4.5 TypeScript

All components ship TypeScript types. Use the exported types when writing wrappers:

import type { SpringButtonProps } from '@spring/ds-react'

interface MyButtonProps extends SpringButtonProps {
  analyticsId: string
}

5. Why the Structure Works This Way

5.1 The core design decision: package first, gallery second

The design system is a package that ships components — not an app that happens to have components. This means:

  • packages/spring-ds-react owns all reusable UI. It can be installed in any Next.js app with npm install.
  • apps/design-library documents and previews those components. It is a consumer, not a definer.
  • apps/personal-loan is a production-style consuming app — it proves the package works in a real flow.

This separation prevents gallery-specific concerns from bleeding into the shipped component API.

5.2 Token pipeline: Figma → CSS → Tailwind

design-tokens.mjs (source of truth — edited by hand or pulled from Figma)
    ↓ npm run tokens:sync
css-variables.css (254 --spring-* CSS vars — committed to git)
    ↓ import in layout.tsx
Available as CSS custom properties in all components

design-tokens.mjs
    ↓ imported by tailwind.config.mjs
Tailwind color/spacing/radius utilities (bg-background, p-space-4, etc.)

Why two outputs? CSS variables work with inline styles and runtime theming. Tailwind classes work with the utility-first authoring pattern and tree-shaking. Both are needed.

5.3 Thin route shells

apps/design-library/src/app/(shell)/**/page.tsx files are intentionally one-liners:

// apps/design-library/src/app/(shell)/components/inputs/text-field/page.tsx
import TextFieldPage from '@/gallery/pages/components/inputs/TextFieldPage'
export default TextFieldPage

Why? Next.js route files are part of the framework's filesystem router — they are infrastructure, not logic. Keeping them thin means:

  • All page logic lives in src/gallery/ and is reusable/testable
  • Route reorganizations (moving URLs) don't touch any business logic
  • The gallery can be previewed in isolation without Next.js routing

5.4 AppFlow registry pattern

The 24-step personal loan flow is driven by a single registry array (personalLoanFlow.ts). Each step is a plain object:

{
  order: 3,
  slug: 'loan-amount',
  title: 'How much are you looking to borrow?',
  phase: 'A',
  status: 'new',
  progress: 3,
  Body: LoanAmountScreen,
  sourcePath: 'screens/AppFlowLoanAmountScreen.tsx',
}

Why? The registry is the single source of truth for step order, routing, sidebar state, progress indicators, flow charts, and gallery previews. Changing the order of steps means editing one array, not updating five files.

5.5 Code Connect split

Figma Code Connect files (*.figma.tsx) are placed either:

  • Package-sidepackages/spring-ds-react/src/components/inputs/*.figma.tsx — for all new reusable components and all inputs. These travel with the published package.
  • Gallery-sideapps/design-library/src/gallery/pages/components/**/*.figma.tsx — for legacy navigations, overlays, and SpringSearchBar until a migration is planned.

Why the split? Code Connect for inputs maps Figma design properties to code props — this information is most useful when it's co-located with the component. Gallery-side Code Connect was added before the convention was established and will be migrated package-side in a future pass.


6. How to Add Your Own Component

This is the end-to-end checklist. Follow every step — skipping any one of them will cause a broken route, missing export, or disconnected navigation entry.

Step 1 — Implement in the package

packages/spring-ds-react/src/components/<category>/<ComponentName>.tsx

Categories (in gallery order): inputsnavigationsactionslayout-structureselection-inputimage-iconsfeedback-indicatorsoverlaysliststablesutilitiesmodules

Rules:

  • Use semantic tokens — no raw hex, no arbitrary spacing
  • Prefix with Spring where appropriate
  • Export the component's TypeScript types alongside it
  • Default to Server Component; add 'use client' only if needed
// packages/spring-ds-react/src/components/feedback/SpringToast.tsx
import React from 'react'

export interface SpringToastProps {
  message: string
  variant?: 'success' | 'error' | 'info'
  onDismiss?: () => void
}

export function SpringToast({ message, variant = 'info', onDismiss }: SpringToastProps) {
  return (
    <div className={`rounded-radius-md p-space-4 bg-surface-${variant}`}>
      <span className="text-body-md text-foreground">{message}</span>
    </div>
  )
}

Step 2 — Export from the package barrel

// packages/spring-ds-react/src/index.ts
export { SpringToast } from './components/feedback/SpringToast'
export type { SpringToastProps } from './components/feedback/SpringToast'

Step 3 — Add Figma Code Connect (when applicable)

For any new reusable component, place the Code Connect file beside the component:

packages/spring-ds-react/src/components/feedback/SpringToast.figma.tsx
import figma from '@figma/code-connect'
import { SpringToast } from './SpringToast'

figma.connect(SpringToast, 'https://www.figma.com/design/YOUR_FILE_ID?node-id=XXX', {
  props: {
    variant: figma.enum('variant', { Success: 'success', Error: 'error', Info: 'info' }),
    message: figma.string('message'),
  },
  example: ({ variant, message }) => <SpringToast variant={variant} message={message} />,
  imports: ["import { SpringToast } from '@spring/ds-react'"],
})

Validate: npm run code-connect:parse

Step 4 — Create the gallery documentation page

apps/design-library/src/gallery/pages/components/<category>/<ComponentName>Page.tsx

Use the shared doc primitives:

'use client'
import { SpringToast } from '@spring/ds-react'
import { LibraryDocPageShell, LibraryPageHeader, LibraryPropsTable } from '@/gallery/components/LibraryDocPage'
import { CodeBlock } from '@/gallery/components/CodeBlock'

export default function SpringToastPage() {
  return (
    <LibraryDocPageShell>
      <LibraryPageHeader
        title="SpringToast"
        description="Transient feedback messages for user actions."
        figmaUrl="https://www.figma.com/design/..."
      />
      <section>
        <h2>Live Example</h2>
        <SpringToast variant="success" message="Application submitted!" />
      </section>
      <CodeBlock language="tsx">{`<SpringToast variant="success" message="Application submitted!" />`}</CodeBlock>
      <LibraryPropsTable props={[
        { name: 'message', type: 'string', required: true, description: 'Toast content' },
        { name: 'variant', type: "'success' | 'error' | 'info'", default: "'info'", description: 'Visual tone' },
        { name: 'onDismiss', type: '() => void', description: 'Dismiss callback' },
      ]} />
    </LibraryDocPageShell>
  )
}

Step 5 — Add the Next.js route shell

// apps/design-library/src/app/library/components/feedback-indicators/spring-toast/page.tsx
import SpringToastPage from '@/gallery/pages/components/feedback/SpringToastPage'
export default SpringToastPage

Step 6 — Add the sidebar navigation entry

// apps/design-library/src/gallery/navigation/libraryNav.ts
// Add under the correct category:
{
  label: 'SpringToast',
  to: '/components/feedback-indicators/spring-toast',
}

Only add to navigation when the page is live and complete. Do not add placeholder-only pages.

Step 7 — Verify

npm run dev                   # check /components/... route renders
npm run build                 # production build must pass
npm run code-connect:parse    # if *.figma.tsx was added
npm run lint                  # no linter errors

7. How Production Builds Work

7.1 Build commands

CommandWhat it does
npm run buildBuilds apps/design-library + apps/personal-loan (Next.js next build)
npm run build:design-libraryBuilds only the gallery app
npm run build:personal-loanBuilds only the standalone personal-loan app
npm run previewServes the built gallery on port 5180
npm run preview:personal-loanServes the personal-loan app on port 5181

7.2 How the package is consumed without a build step

Inside the monorepo, apps/design-library and apps/personal-loan import @spring/ds-react directly from source via next.config.ts aliases:

// apps/design-library/next.config.ts
const nextConfig = {
  transpilePackages: ['@spring/ds-react', '@spring/personal-loan-appflow'],
  webpack: (config) => {
    config.resolve.alias = {
      '@spring/ds-react': path.resolve(__dirname, '../../packages/spring-ds-react/src/index.ts'),
      // ...
    }
    return config
  },
}

This means there is no separate tsup build step needed for local development. Next.js transpiles the TypeScript package source directly. When publishing externally, tsup.config.ts builds the dist artifacts.

7.3 Token change workflow (required before production build)

Whenever design-tokens.mjs is edited:

# 1. Rebuild CSS variables
npm run tokens:sync

# 2. Restart the dev server (CSS vars are loaded at startup)
# Kill and restart: npm run dev

# 3. Before production build validation, clear the Next.js cache
rm -rf apps/design-library/.next

# 4. Build
npm run build

Skipping the .next cache clear after a token change can result in stale CSS variables in the production build.

7.4 Deployment

The gallery app is configured for Vercel (vercel.json at repo root). The personal-loan app can be deployed independently.

Environment variables needed for Figma sync (not for production app runtime):

  • FIGMA_TOKEN — Figma personal access token
  • FIGMA_FILE_ID — Foundation file for token pull

7.5 What ships in the published npm package

When packages/spring-ds-react is published to Gemfury, tsup builds:

  • dist/index.js — all components
  • dist/icons/index.js — icon tree
  • dist/rules/index.js — design rules
  • dist/appflow/index.js — AppFlow registry (bundled from workspace package)
  • dist/tokens/ — token artifacts (copied verbatim from src/tokens/)

The token CSS file (generated/css-variables.css) is published as a static asset and imported directly by consumers.


8. The Agentic Workflow

8.1 What it is

The agentic workflow is a structured, outcome-first protocol for AI-assisted design-to-code work. It defines how AI agents (and human contributors) classify requests, choose file targets, build outputs, and validate results before handoff.

It is documented in docs/ai/ and codified as executable .cursor/rules/ and .cursor/skills/.

8.2 The seven outcome types

Every request must be classified before any implementation begins:

TypeWhen to usePrimary target
A — Reusable componentResult should be imported from @spring/ds-reactpackages/spring-ds-react/src/components/
B — Gallery doc pageDocumenting or demonstrating existing UIapps/design-library/src/gallery/pages/
C — AppFlow screenPersonal loan flow stepapps/design-library/src/gallery/page/appflow/screens/
D — Static library pageInformational, foundation, or guide contentapps/design-library/src/gallery/pages/
E — Token/foundation changeVisual system changepackages/spring-ds-react/src/tokens/design-tokens.mjs
F — Icon changeAdding or regenerating iconspackages/spring-ds-react/src/icons/
G — Documentation/rule changeOperating guidance updatedocs/ or .cursor/rules/

8.3 The five-agent model

A single AI instance can play multiple roles, but the responsibilities must remain distinct:

RoleResponsibilityCannot
Workflow ConductorIntake, classification, approval gates, handoffSkip user validation; hide failures
Design & Token AnalystRead Figma context; map values to tokens; report gapsWrite component syntax; add raw values
Structure & Reuse ArchitectDetermine reuse vs. create; audit exports and registriesCreate new components when existing ones suffice
Implementation BuilderBuild only the approved outcome and injection pointsPut logic in route shells; call production APIs in gallery
Guardrail CriticRun validation; classify failures; limit correction loopsMark work complete without named validation coverage

8.4 The deterministic workflow loop

Inbound request
  → Classify outcome type
  → Check for missing context (ask if critical context is absent)
  → Investigate existing structure (audit exports, routes, registry)
  → Token and design normalization (map to tokens; report gaps)
  → Build plan + file map (stop for user approval when uncertain)
  → [User approval gate]
  → Build outcome files
  → Inject routes, registries, exports, nav
  → Guardrail validation (max 3 correction loops)
  → Developer handoff with WorkflowRunState

8.5 Core guardrails

  • File boundary — components stay in the package; gallery docs stay in apps/design-library/src/gallery; routes stay under apps/design-library/src/app/(shell)
  • Server Components by default — add 'use client' only when necessary
  • Registry completeness — every new component/screen must be wired into all required injection points
  • Placeholder policy — do not add incomplete pages to navigation
  • Three-loop limit — stop and escalate after three failed validation attempts

8.6 Reading order for agents

docs/ai/project-context.md         ← classify and route the request
docs/ai/operational-manifesto.md   ← workflow, roles, guardrails
docs/ai/workflow-contracts.md      ← file targets and injection contracts
docs/ai/review-checklist.md        ← quality gate before handoff

8.7 Handoff artifact

A completed workflow must state:

  • Route/import path: /components/...
  • Known risks: [any dirty-tree caveats or skipped validations]

---

## 9. Building a Full Complex Repo With This Component Set

This section walks through how to build a production-grade Next.js application using Spring DS as the component foundation.

### 9.1 Project setup

```bash
# Create your app
npx create-next-app@latest my-loan-app --typescript --tailwind --app

# Install Spring DS
cd my-loan-app
npm install spring-design-library-react

9.2 Configure Next.js to transpile the package

// next.config.ts
import type { NextConfig } from 'next'

const nextConfig: NextConfig = {
  transpilePackages: ['spring-design-library-react'],
}

export default nextConfig

9.3 Wire tokens into Tailwind

// tailwind.config.mjs
import { tokens } from 'spring-design-library-react/tokens'

export default {
  content: [
    './src/**/*.{ts,tsx}',
    './node_modules/spring-design-library-react/src/**/*.{ts,tsx}',
  ],
  theme: {
    extend: {
      colors: {
        background: `hsl(var(--spring-semantic-color-bg-canvas) / <alpha-value>)`,
        foreground: `hsl(var(--spring-semantic-color-text-primary) / <alpha-value>)`,
        primary: `hsl(var(--spring-semantic-color-bg-brand) / <alpha-value>)`,
        // extend with your own brand aliases as needed
      },
    },
  },
}

9.4 Import the CSS variables in your root layout

// src/app/layout.tsx
import 'spring-design-library-react/tokens/generated/css-variables.css'
import './globals.css'

9.5 Recommended folder structure for a complex app

my-loan-app/
├── src/
│   ├── app/                         ← Next.js App Router routes (thin shells)
│   │   ├── layout.tsx               ← token CSS + font + providers
│   │   ├── (marketing)/             ← route group for public pages
│   │   │   ├── page.tsx
│   │   │   └── about/page.tsx
│   │   └── (application)/           ← route group for authenticated flow
│   │       ├── layout.tsx           ← auth guard, app shell
│   │       └── apply/
│   │           ├── page.tsx         ← start screen
│   │           └── [step]/page.tsx  ← dynamic flow steps
│   ├── features/                    ← domain-specific feature modules
│   │   ├── application/             ← loan application feature
│   │   │   ├── components/          ← feature-level components (extend Spring DS)
│   │   │   ├── hooks/               ← useApplicationState, useLoanCalculator
│   │   │   ├── services/            ← API calls, data fetching
│   │   │   └── screens/             ← full screen compositions
│   │   └── auth/
│   │       ├── components/
│   │       └── hooks/
│   ├── components/                  ← app-wide shared components
│   │   ├── AppHeader.tsx            ← wraps SpringHeader with app-specific data
│   │   ├── AppFooter.tsx
│   │   └── FormField.tsx            ← wraps TextField with validation
│   ├── lib/                         ← utilities, constants, types
│   │   ├── api.ts
│   │   ├── validation.ts            ← zod schemas
│   │   └── types.ts
│   └── styles/
│       └── globals.css

9.6 Building a multi-step flow

// src/features/application/screens/LoanAmountScreen.tsx
'use client'
import { LoanAmountSlider, SpringButton } from 'spring-design-library-react'
import { useState } from 'react'

interface LoanAmountScreenProps {
  defaultAmount?: number
  onNext: (amount: number) => void
  onBack: () => void
}

export function LoanAmountScreen({ defaultAmount = 5000, onNext, onBack }: LoanAmountScreenProps) {
  const [amount, setAmount] = useState(defaultAmount)

  return (
    <div className="flex flex-col gap-space-8 max-w-stage mx-auto px-space-4">
      <h1 className="text-heading-xl text-foreground">
        How much would you like to borrow?
      </h1>
      <LoanAmountSlider
        value={amount}
        min={1000}
        max={35000}
        onChange={setAmount}
      />
      <div className="flex gap-space-4">
        <SpringButton variant="outlined" size="lg" onClick={onBack}>Back</SpringButton>
        <SpringButton variant="primary" size="lg" block onClick={() => onNext(amount)}>
          Continue
        </SpringButton>
      </div>
    </div>
  )
}
// src/app/(application)/apply/[step]/page.tsx
import { notFound } from 'next/navigation'
import { LoanAmountScreen } from '@/features/application/screens/LoanAmountScreen'

const STEP_COMPONENTS: Record<string, React.ComponentType<any>> = {
  'loan-amount': LoanAmountScreen,
  // ... other steps
}

export default function StepPage({ params }: { params: { step: string } }) {
  const Screen = STEP_COMPONENTS[params.step]
  if (!Screen) notFound()
  return <Screen />
}

9.7 Separating presentation from data

The Spring DS components and AppFlow screens are purely presentational. Keep data concerns out of components:

// ✅ Correct — data fetching in a Server Component above the screen
async function ApplicationPage({ params }: { params: { step: string } }) {
  const application = await fetchApplication(params.applicationId) // server-side
  return <LoanAmountScreen defaultAmount={application.requestedAmount} onNext={...} />
}

// ❌ Wrong — data fetching inside a presentation screen
function LoanAmountScreen() {
  const { data } = useSWR('/api/application') // this couples UI to data layer
  // ...
}

9.8 Extending components without forking

Wrap Spring DS components to add app-specific behavior:

// src/components/FormField.tsx
'use client'
import { TextField, type TextFieldProps } from 'spring-design-library-react'
import { useFormContext } from 'react-hook-form'

interface FormFieldProps extends Omit<TextFieldProps, 'value' | 'onChange' | 'status' | 'errorMessage'> {
  name: string
}

export function FormField({ name, ...props }: FormFieldProps) {
  const { register, formState: { errors } } = useFormContext()
  const { ref, ...rest } = register(name)

  return (
    <TextField
      {...props}
      {...rest}
      inputRef={ref}
      status={errors[name] ? 'error' : 'default'}
      errorMessage={errors[name]?.message as string}
    />
  )
}

10. What to Do and What Not to Do

Do

PracticeWhy
Import from @spring/ds-react (or the published package root)Ensures you use the public API, not internal implementation details
Use semantic token classes (bg-background, text-foreground)Token classes are versioned; raw hex breaks on theme updates
Import css-variables.css in your root layoutAll Spring components depend on these CSS custom properties
Default to Server Components; add 'use client' only when neededImproves performance; avoids unnecessary client bundles
Keep route files thin (app/**/page.tsx as one-liners)Separates routing infrastructure from UI logic
Run npm run tokens:sync after editing design-tokens.mjsKeeps generated CSS in sync with the source
Clear .next/ before production build validation after token changesPrevents stale CSS in the cache
Run npm run code-connect:parse after editing *.figma.tsxValidates Code Connect mappings before publishing to Figma
Export component types alongside the componentEnables consumers to write strongly-typed wrappers
Register AppFlow steps in personalLoanFlow.tsKeeps sidebar, routing, flow charts, and progress indicators in sync
Wrap Spring DS components when adding app-specific behaviorAvoids forking the package; updates compose cleanly

Do Not

Anti-patternWhy it breaks things
Import from package internals (../../packages/spring-ds-react/src/...)Bypasses the public API; breaks on refactoring
Use raw hex colors or arbitrary Tailwind values (bg-[#1A1A2E])Disconnects from the token system; visual inconsistency at scale
Skip the css-variables.css importComponents will render without colors, spacing, or typography
Put production API calls, analytics, or database access inside gallery pages or AppFlow screensGallery is a documentation layer; it cannot call production services
Add placeholder-only pages to sidebar navigationUsers encounter dead-end routes; erodes trust in the design system
Modify multiple unrelated files during a single feature additionProduces unclear diffs, harder rollbacks, and obscured responsibility
Revert unrelated user changes during automated agentic workflowsDestroys concurrent work
Define component API inside gallery pages (e.g. local type definitions that mirror the component)Creates drift between package and gallery; types go stale
Hand-edit generated/css-variables.css directlyOverwritten on next tokens:sync
Hand-edit the icons/ directoryOverwritten on next icons:sync
Add 'use client' to route shellsRoute shells should be Server Components; client boundaries belong in the gallery implementation
Skip npm run build before raising a PRProduction builds surface errors that dev mode silently ignores

11. Production Readiness Evaluation

This section honestly evaluates the library's current state for complex production workflows.

11.1 Strengths

AreaAssessment
Token systemStrong. 254 CSS variables, Zod-validated, two-output pipeline (CSS + JS), full Figma sync. Covers primitives, semantics, and component-level tokens.
Component APIConsistent patterns across all categories. forwardRef on interactive components, TypeScript types exported, Figma property tables documented in component headers.
Gallery documentationComprehensive. Every component has a live playground, states grid, props table, and code block. Replaces Storybook effectively for this team's scale.
AppFlow registryElegant. Single registry drives routing, sidebar, progress, flow charts, and previews. Adding a step is a one-file change.
Agentic workflowMature. Five-role model, outcome types, three-loop guardrail, handoff artifacts — this is a production-grade AI workflow, not an ad-hoc prompt.
Monorepo structureClean. Clear package/app boundary. Thin shells, gallery implementations, package-first components.
Code Connect20 files covering all inputs and major components. Figma config validated via CLI. Enables designers to inspect real component usage.

11.2 Current gaps (known)

GapSeverityNotes
No package build step for external publishMediumApps consume source via Next.js aliases. Publishing externally requires tsup dist build. This is documented but not automated.
Code Connect splitMediumInputs are package-side; navigations, overlays, and SpringSearchBar are legacy gallery-side. A migration pass is needed for consistency.
Empty component categoriesLowlayout-structure, lists, tables, image-icons, utilities exist as stubs. No components yet. Reserved for future Figma targets.
No TurborepoLowAcceptable for current monorepo size (2 apps, 2 packages). Parallel builds and caching become important with 4+ workspaces.
No automated component testsMediumNo unit or visual regression tests. Production confidence relies on manual QA (npm run build + gallery review).
No versioning strategyMediumspring-design-library-react is at 0.0.1. No semantic versioning policy, no changelog, no deprecation process.
No dark modeInfoToken structure supports a .dark class (defined in Tailwind config) but no dark-mode token set is populated.
Placeholder gallery routesLow~12 routes exist but are hidden from navigation. Direct URL access returns a content-free page.

11.3 Verdict by use case

Use caseReadiness
Internal team building loan flow screensProduction-ready — the AppFlow system and component set are purpose-built for this
Cross-team design system consumption (same org)Ready with setup — publish to Gemfury, document token setup, establish versioning
Open-source or external npm distributionNot yet — needs dist build, changelog, semver, deprecation policy
Multi-brand theming (partner DPP)Architecture supports it — Tailwind config is token-driven; partner token overrides are planned but not implemented
Design-to-code AI generationAhead of the curve — Code Connect + agentic workflow + token AI reference map is a strong foundation
Team with no prior React/Next.js experienceNeeds companion guide — this document addresses the gap, but onboarding time is real

12. Scalability Reflection

These are the architectural improvements to consider as the system grows.

12.1 What works well and will continue to scale

  • Token pipeline — the design-tokens.mjs → tokens:sync → css-variables.css pipeline is deterministic and composable. It can grow to 500+ tokens without structural change.
  • Registry patternpersonalLoanFlow.ts as single source of truth is excellent. Extend to more flows by creating additional *Flow.ts registries following the same shape.
  • Thin route shells — the shell/implementation split means URL restructuring is always a one-file change. This is scalable to 100+ routes.
  • Outcome-typed agentic workflow — classifying requests before building prevents scope creep. This scales with team size.

12.2 Where friction will grow

Tailwind content scanning

As the monorepo gains more packages, the content array in tailwind.config.mjs must be updated manually:

content: [
  './src/**/*.{ts,tsx}',
  '../../packages/spring-ds-react/src/**/*.{ts,tsx}',
  '../../packages/personal-loan-appflow/src/**/*.{ts,tsx}',
  // each new package must be added here
]

Recommendation: Introduce a shared Tailwind preset package (@spring/tailwind-config) that apps extend. This moves the content paths into one maintainable location.

Package consumption without a build step

The current next.config.ts alias approach works inside the monorepo but is incompatible with external consumers. As the team adds more consuming apps (even internally), each needs the same manual transpilePackages + alias setup.

Recommendation: Add a proper tsup dist build that runs as part of npm run build. Publish dist/ artifacts. Remove the need for consuming apps to alias the package source.

No versioning or changelog

At 0.0.1, breaking changes to component APIs are silently absorbed by all apps in the monorepo. As soon as a second team consumes the package, an untracked API change will break their build.

Recommendation: Adopt semantic versioning, generate changelogs from conventional commits, and establish a deprecation notice period (minimum one minor version).

Component test coverage

The lack of automated tests means every component change requires manual gallery review. With 20+ components and 5 reserved categories still to implement, this is a growing QA burden.

Recommendation (in order of effort):

  1. Snapshot tests for token output (tokens:sync produces identical output)
  2. TypeScript strict mode on all component files
  3. @testing-library/react unit tests for interactive components (inputs, modals)
  4. Visual regression testing (Chromatic or Percy) once the component set stabilizes

CSS variable naming collision risk

All 254 CSS variables use the --spring- prefix. As partner DPP tokens are introduced, a convention for namespacing brand-specific overrides is needed to avoid collisions.

Recommendation: Reserve --spring-brand-* for partner token overrides. Document the override surface in the token system.

AppFlow registry coupling

personalLoanFlow.ts is the single registry for step order, routing, and previews. If two different products need different flows (e.g. auto loan, home equity), duplicating the registry pattern will work but creates maintenance overhead.

Recommendation: Generalize personalLoanFlow.ts into a typed FlowRegistry<T> factory. The existing structure is already close — extracting the type from PERSONAL_LOAN_STEPS and creating createFlowRegistry(steps) would allow any product flow to use the same shell infrastructure.

Monorepo tooling

npm workspaces has no build caching, no parallel execution, and no dependency graph. npm run build rebuilds everything serially.

Recommendation: Introduce Turborepo when the monorepo reaches 4+ packages. The migration is low-risk — add turbo.json, replace root build/dev scripts. The existing structure maps cleanly onto Turborepo's pipeline model.

12.3 Priority order for scalability improvements

PriorityImprovementEffort
1Package dist build + versioningMedium
2Shared Tailwind preset packageLow
3Interactive component unit testsMedium
4FlowRegistry<T> generalizationLow
5TurborepoLow
6Visual regression testingHigh
7Multi-brand token namespacingMedium

Quick Reference

Essential commands

npm install                   # install workspace dependencies
npm run dev                   # start design gallery at localhost:3000
npm run dev:personal-loan     # start standalone personal-loan app
npm run build                 # production build (both apps)
npm run tokens:sync           # regenerate CSS variables from design-tokens.mjs
npm run tokens:figma          # pull Figma Variables into figma-variables.json
npm run icons:sync            # sync icon components from Figma
npm run code-connect:parse    # validate Figma Code Connect files
npm run code-connect:publish  # publish Code Connect to Figma
npm run lint                  # ESLint across the workspace

Key file locations

What you needWhere to find it
Token source of truthpackages/spring-ds-react/src/tokens/design-tokens.mjs
CSS variables (generated)packages/spring-ds-react/src/tokens/generated/css-variables.css
Component sourcepackages/spring-ds-react/src/components/<category>/
Public component exportspackages/spring-ds-react/src/index.ts
Gallery doc pagesapps/design-library/src/gallery/pages/
Route shellsapps/design-library/src/app/(shell)/
Sidebar navigationapps/design-library/src/gallery/navigation/libraryNav.ts
AppFlow step registrypackages/personal-loan-appflow/src/personalLoanFlow.ts
AppFlow screensapps/design-library/src/gallery/page/appflow/screens/
Tailwind configapps/design-library/tailwind.config.mjs
AI workflow docsdocs/ai/
Component addition checklistdocs/COMPONENT_ADDITION.md

Related documentation

DocumentAudiencePurpose
docs/REPOSITORY_STRUCTURE.mdAll developersCanonical repo map
docs/CONTRIBUTING.mdContributorsSetup + PR workflow
docs/COMPONENT_ADDITION.mdComponent authorsStep-by-step checklist
docs/PLACEHOLDER_POLICY.mdGallery maintainersRoute placeholder conventions
docs/ai/project-context.mdAI agentsIntake protocol + outcome routing
docs/ai/operational-manifesto.mdAI agents / workflowRoles, guardrails, workflow loop
docs/ai/workflow-contracts.mdAI agents / maintainersFile targets + injection contracts
docs/ai/review-checklist.mdAI agentsPre-handoff quality gate
packages/spring-ds-react/README.mdDS authorsPackage entry points + Figma rules
apps/design-library/README.mdApp developersApp routing + integration