Design Engineering
Build

Component Development

Build components against their specifications with correct prop APIs, variant handling, accessibility attributes, and test coverage.

AI agents excel at component scaffolding once the spec is defined. Give the agent a component specification — prop interface, variant map, accessibility requirements — and let it generate the implementation. For repetitive families (form fields, table columns, modal variants), define the pattern once: "Generate all remaining [type] following this pattern." Always verify server/client boundary and token usage.

Implementation follows the component spec from Design & Architecture. Complete spec → mechanical implementation. Incomplete spec → inconsistent design decisions across developers.

Implementation Order

Build bottom-up: primitives → composites → sections. Never build sections before primitives — leads to inline styles, duplicated logic, mismatched tokens.

Design tokens — Define all tokens in globals.css

Primitives — Button, Input, Badge. All variants and states, full test coverage

Composites — FormField, DataTable. Built from primitives, no new base styles

Layout templates — DashboardLayout, AuthLayout. Slot-based, no business logic

Feature sections — ProjectCard, TaskList. Domain-aware, uses composites + primitives

Pages — Assemble sections into layouts, wire data

Server vs Client Components

Every component is a Server Component by default. Add "use client" only when needed.

Server ComponentClient Component
Fetching data directly from DB/APIuseState, useEffect, useRef
Static or server-computed contentUser events (onClick, onSubmit)
Server-only env varsBrowser APIs (localStorage, navigator)
Large dependencies that shouldn't ship to clientThird-party libs that require DOM
SEO-critical contentInteractive UI: forms, modals, dropdowns
Correct boundary placement
// app/dashboard/page.tsx — Server Component (default)
import { getProjects } from "@/lib/data"
import { ProjectList } from "@/components/projects/project-list"

export default async function DashboardPage() {
  // Data fetching happens on the server — no useEffect needed
  const projects = await getProjects()
  return <ProjectList initialProjects={projects} />
}

// components/projects/project-list.tsx
"use client"  // Only added because this component uses useState for filters

import { useState } from "react"

export function ProjectList({ initialProjects }) {
  const [filter, setFilter] = useState("all")
  // ...
}

Variant Handling with CVA

Use class-variance-authority (CVA) for type-safe variant props, colocated with the component.

Accessibility in Implementation

Accessibility goes in at the component level, not retrofitted. Non-negotiable for every interactive component:

  • Keyboard: all elements reachable via Tab, activated Enter/Space, dismissed Escape
  • Focus: modal opens → focus moves in. Modal closes → focus returns to trigger
  • ARIA: semantic HTML first (button, nav, dialog). ARIA only when no native element fits
  • Live regions: dynamic updates (toasts, form errors) use aria-live
  • Labels: every form control has an associated <label> — not just placeholder

Error Boundaries

Wrap independent UI sections so one component failure doesn't crash the page. Use error.tsx for route-level boundaries, React's ErrorBoundary for component-level isolation.

app/dashboard/error.tsx
"use client"

interface ErrorPageProps {
  error: Error & { digest?: string }
  reset: () => void
}

export default function DashboardError({ error, reset }: ErrorPageProps) {
  return (
    <div role="alert" className="flex flex-col items-center gap-4 py-16">
      <h2 className="text-lg font-semibold">Something went wrong</h2>
      <p className="text-sm text-muted-foreground max-w-md text-center">
        {error.message || "An unexpected error occurred loading the dashboard."}
      </p>
      <button onClick={reset} className="text-sm underline underline-offset-4">
        Try again
      </button>
    </div>
  )
}

On this page