red64-cli 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (125) hide show
  1. package/README.md +1 -2
  2. package/dist/cli/parseArgs.d.ts.map +1 -1
  3. package/dist/cli/parseArgs.js +5 -0
  4. package/dist/cli/parseArgs.js.map +1 -1
  5. package/dist/components/init/CompleteStep.d.ts.map +1 -1
  6. package/dist/components/init/CompleteStep.js +2 -2
  7. package/dist/components/init/CompleteStep.js.map +1 -1
  8. package/dist/components/init/TestCheckStep.d.ts +16 -0
  9. package/dist/components/init/TestCheckStep.d.ts.map +1 -0
  10. package/dist/components/init/TestCheckStep.js +120 -0
  11. package/dist/components/init/TestCheckStep.js.map +1 -0
  12. package/dist/components/init/index.d.ts +1 -0
  13. package/dist/components/init/index.d.ts.map +1 -1
  14. package/dist/components/init/index.js +1 -0
  15. package/dist/components/init/index.js.map +1 -1
  16. package/dist/components/init/types.d.ts +9 -0
  17. package/dist/components/init/types.d.ts.map +1 -1
  18. package/dist/components/screens/InitScreen.d.ts.map +1 -1
  19. package/dist/components/screens/InitScreen.js +69 -6
  20. package/dist/components/screens/InitScreen.js.map +1 -1
  21. package/dist/components/screens/ListScreen.d.ts.map +1 -1
  22. package/dist/components/screens/ListScreen.js +28 -3
  23. package/dist/components/screens/ListScreen.js.map +1 -1
  24. package/dist/components/screens/StartScreen.d.ts.map +1 -1
  25. package/dist/components/screens/StartScreen.js +212 -13
  26. package/dist/components/screens/StartScreen.js.map +1 -1
  27. package/dist/components/ui/ArtifactsSidebar.d.ts +19 -0
  28. package/dist/components/ui/ArtifactsSidebar.d.ts.map +1 -0
  29. package/dist/components/ui/ArtifactsSidebar.js +51 -0
  30. package/dist/components/ui/ArtifactsSidebar.js.map +1 -0
  31. package/dist/components/ui/FeatureSidebar.d.ts.map +1 -1
  32. package/dist/components/ui/FeatureSidebar.js +1 -1
  33. package/dist/components/ui/FeatureSidebar.js.map +1 -1
  34. package/dist/components/ui/index.d.ts +1 -0
  35. package/dist/components/ui/index.d.ts.map +1 -1
  36. package/dist/components/ui/index.js +1 -0
  37. package/dist/components/ui/index.js.map +1 -1
  38. package/dist/services/ClaudeErrorDetector.js +3 -3
  39. package/dist/services/ClaudeErrorDetector.js.map +1 -1
  40. package/dist/services/ConfigService.d.ts +1 -0
  41. package/dist/services/ConfigService.d.ts.map +1 -1
  42. package/dist/services/ConfigService.js.map +1 -1
  43. package/dist/services/ProjectDetector.d.ts +28 -0
  44. package/dist/services/ProjectDetector.d.ts.map +1 -0
  45. package/dist/services/ProjectDetector.js +236 -0
  46. package/dist/services/ProjectDetector.js.map +1 -0
  47. package/dist/services/TestRunner.d.ts +46 -0
  48. package/dist/services/TestRunner.d.ts.map +1 -0
  49. package/dist/services/TestRunner.js +85 -0
  50. package/dist/services/TestRunner.js.map +1 -0
  51. package/dist/services/index.d.ts +2 -0
  52. package/dist/services/index.d.ts.map +1 -1
  53. package/dist/services/index.js +2 -0
  54. package/dist/services/index.js.map +1 -1
  55. package/dist/types/index.d.ts +13 -0
  56. package/dist/types/index.d.ts.map +1 -1
  57. package/dist/types/index.js.map +1 -1
  58. package/framework/.red64/settings/templates/specs/gap-analysis.md +163 -0
  59. package/framework/agents/claude/.claude/agents/red64/spec-impl.md +131 -2
  60. package/framework/agents/claude/.claude/agents/red64/validate-gap.md +13 -7
  61. package/framework/agents/claude/.claude/commands/red64/spec-impl.md +24 -0
  62. package/framework/agents/claude/.claude/commands/red64/validate-gap.md +4 -0
  63. package/framework/agents/codex/.codex/agents/red64/spec-impl.md +131 -2
  64. package/framework/agents/codex/.codex/agents/red64/validate-gap.md +13 -7
  65. package/framework/agents/codex/.codex/commands/red64/spec-impl.md +24 -0
  66. package/framework/agents/codex/.codex/commands/red64/validate-gap.md +4 -0
  67. package/framework/stacks/generic/feedback.md +80 -0
  68. package/framework/stacks/nextjs/accessibility.md +437 -0
  69. package/framework/stacks/nextjs/api.md +431 -0
  70. package/framework/stacks/nextjs/coding-style.md +282 -0
  71. package/framework/stacks/nextjs/commenting.md +226 -0
  72. package/framework/stacks/nextjs/components.md +411 -0
  73. package/framework/stacks/nextjs/conventions.md +333 -0
  74. package/framework/stacks/nextjs/css.md +310 -0
  75. package/framework/stacks/nextjs/error-handling.md +442 -0
  76. package/framework/stacks/nextjs/feedback.md +124 -0
  77. package/framework/stacks/nextjs/migrations.md +332 -0
  78. package/framework/stacks/nextjs/models.md +362 -0
  79. package/framework/stacks/nextjs/queries.md +410 -0
  80. package/framework/stacks/nextjs/responsive.md +338 -0
  81. package/framework/stacks/nextjs/tech-stack.md +177 -0
  82. package/framework/stacks/nextjs/test-writing.md +475 -0
  83. package/framework/stacks/nextjs/validation.md +467 -0
  84. package/framework/stacks/python/api.md +468 -0
  85. package/framework/stacks/python/authentication.md +342 -0
  86. package/framework/stacks/python/code-quality.md +283 -0
  87. package/framework/stacks/python/code-refactoring.md +315 -0
  88. package/framework/stacks/python/coding-style.md +462 -0
  89. package/framework/stacks/python/conventions.md +399 -0
  90. package/framework/stacks/python/error-handling.md +512 -0
  91. package/framework/stacks/python/feedback.md +92 -0
  92. package/framework/stacks/python/implement-ai-llm.md +468 -0
  93. package/framework/stacks/python/migrations.md +388 -0
  94. package/framework/stacks/python/models.md +399 -0
  95. package/framework/stacks/python/python.md +232 -0
  96. package/framework/stacks/python/queries.md +451 -0
  97. package/framework/stacks/python/structure.md +245 -58
  98. package/framework/stacks/python/tech.md +92 -35
  99. package/framework/stacks/python/testing.md +380 -0
  100. package/framework/stacks/python/validation.md +471 -0
  101. package/framework/stacks/rails/authentication.md +176 -0
  102. package/framework/stacks/rails/code-quality.md +287 -0
  103. package/framework/stacks/rails/code-refactoring.md +299 -0
  104. package/framework/stacks/rails/feedback.md +130 -0
  105. package/framework/stacks/rails/implement-ai-llm-with-rubyllm.md +342 -0
  106. package/framework/stacks/rails/rails.md +301 -0
  107. package/framework/stacks/rails/rails8-best-practices.md +498 -0
  108. package/framework/stacks/rails/rails8-css.md +573 -0
  109. package/framework/stacks/rails/structure.md +140 -0
  110. package/framework/stacks/rails/tech.md +108 -0
  111. package/framework/stacks/react/code-quality.md +521 -0
  112. package/framework/stacks/react/components.md +625 -0
  113. package/framework/stacks/react/data-fetching.md +586 -0
  114. package/framework/stacks/react/feedback.md +110 -0
  115. package/framework/stacks/react/forms.md +694 -0
  116. package/framework/stacks/react/performance.md +640 -0
  117. package/framework/stacks/react/product.md +22 -9
  118. package/framework/stacks/react/state-management.md +472 -0
  119. package/framework/stacks/react/structure.md +351 -44
  120. package/framework/stacks/react/tech.md +219 -30
  121. package/framework/stacks/react/testing.md +690 -0
  122. package/package.json +1 -1
  123. package/framework/stacks/node/product.md +0 -27
  124. package/framework/stacks/node/structure.md +0 -82
  125. package/framework/stacks/node/tech.md +0 -63
@@ -0,0 +1,333 @@
1
+ # Project Conventions
2
+
3
+ Next.js 15 App Router file conventions, folder structure, environment management, and development workflow.
4
+
5
+ ---
6
+
7
+ ## Philosophy
8
+
9
+ - **Convention over configuration**: Follow Next.js file-based conventions; avoid custom routing
10
+ - **Colocation**: Keep related files together; tests next to source, types next to usage
11
+ - **Explicit boundaries**: Every route segment has its own loading, error, and not-found states
12
+ - **Environment safety**: Type-safe env vars, never leak secrets to the client
13
+
14
+ ---
15
+
16
+ ## App Router File Conventions
17
+
18
+ ### Special Files
19
+
20
+ | File | Purpose | Required |
21
+ |---|---|---|
22
+ | `page.tsx` | Route UI, makes segment publicly accessible | Yes (for routes) |
23
+ | `layout.tsx` | Shared UI wrapper, persists across navigations | Root required |
24
+ | `loading.tsx` | Loading UI shown while page/segment loads | Recommended |
25
+ | `error.tsx` | Error boundary for the segment | Recommended |
26
+ | `not-found.tsx` | UI for `notFound()` calls | Recommended |
27
+ | `template.tsx` | Like layout but re-mounts on navigation | Rare |
28
+ | `default.tsx` | Parallel route fallback | When using parallel routes |
29
+ | `route.ts` | API endpoint (GET, POST, etc.) | For API routes |
30
+ | `global-error.tsx` | Root-level error boundary (wraps `<html>`) | Recommended |
31
+ | `middleware.ts` | Runs before every request (at project root) | As needed |
32
+
33
+ ### File Naming
34
+
35
+ ```
36
+ app/
37
+ layout.tsx # Root layout (required)
38
+ page.tsx # Home page (/)
39
+ loading.tsx # Global loading state
40
+ error.tsx # Global error boundary
41
+ not-found.tsx # Global 404 page
42
+ global-error.tsx # Root error boundary
43
+
44
+ dashboard/
45
+ layout.tsx # Dashboard layout (sidebar, nav)
46
+ page.tsx # /dashboard
47
+ loading.tsx # Dashboard loading skeleton
48
+ error.tsx # Dashboard error boundary
49
+
50
+ settings/
51
+ page.tsx # /dashboard/settings
52
+
53
+ users/
54
+ page.tsx # /dashboard/users
55
+ [id]/
56
+ page.tsx # /dashboard/users/:id
57
+ edit/
58
+ page.tsx # /dashboard/users/:id/edit
59
+
60
+ (auth)/ # Route group (no URL segment)
61
+ login/
62
+ page.tsx # /login
63
+ register/
64
+ page.tsx # /register
65
+
66
+ api/
67
+ health/
68
+ route.ts # GET /api/health
69
+ users/
70
+ route.ts # GET, POST /api/users
71
+ [id]/
72
+ route.ts # GET, PATCH, DELETE /api/users/:id
73
+ ```
74
+
75
+ ---
76
+
77
+ ## Folder Structure
78
+
79
+ ### Full Project Layout
80
+
81
+ ```
82
+ src/
83
+ app/ # Next.js routes and layouts
84
+ components/
85
+ ui/ # Reusable primitives (Button, Input, Card)
86
+ forms/ # Form-specific components
87
+ layouts/ # Layout building blocks (Sidebar, Header)
88
+ lib/
89
+ prisma.ts # Prisma client singleton
90
+ auth.ts # NextAuth configuration
91
+ utils.ts # General utilities
92
+ hooks/ # Custom React hooks
93
+ actions/ # Server actions
94
+ types/ # Shared TypeScript types
95
+ prisma/
96
+ schema.prisma # Database schema
97
+ migrations/ # Migration history
98
+ seed.ts # Seed data script
99
+ public/ # Static assets (favicons, og images)
100
+ tests/
101
+ e2e/ # Playwright end-to-end tests
102
+ ```
103
+
104
+ ### Naming Patterns
105
+
106
+ | Entity | Convention | Example |
107
+ |---|---|---|
108
+ | Route directories | kebab-case | `user-settings/`, `api-keys/` |
109
+ | Component files | kebab-case | `user-card.tsx`, `data-table.tsx` |
110
+ | Utility files | kebab-case | `format-date.ts`, `cn.ts` |
111
+ | Hook files | kebab-case with use- prefix | `use-debounce.ts` |
112
+ | Action files | kebab-case | `user-actions.ts` |
113
+ | Type files | kebab-case | `api-types.ts` |
114
+ | Constants | kebab-case file, UPPER_SNAKE exports | `config.ts` with `MAX_RETRIES` |
115
+
116
+ ---
117
+
118
+ ## Route Groups and Parallel Routes
119
+
120
+ ### Route Groups (Parentheses)
121
+
122
+ ```
123
+ app/
124
+ (marketing)/ # No URL impact
125
+ layout.tsx # Marketing layout (different nav)
126
+ page.tsx # / (home)
127
+ about/
128
+ page.tsx # /about
129
+ pricing/
130
+ page.tsx # /pricing
131
+
132
+ (dashboard)/ # No URL impact
133
+ layout.tsx # Dashboard layout (sidebar)
134
+ dashboard/
135
+ page.tsx # /dashboard
136
+ settings/
137
+ page.tsx # /dashboard/settings
138
+ ```
139
+
140
+ ### Dynamic Routes
141
+
142
+ ```typescript
143
+ // app/users/[id]/page.tsx
144
+ interface PageProps {
145
+ params: Promise<{ id: string }>;
146
+ }
147
+
148
+ export default async function UserPage({ params }: PageProps) {
149
+ const { id } = await params;
150
+ const user = await prisma.user.findUnique({ where: { id } });
151
+
152
+ if (!user) {
153
+ notFound();
154
+ }
155
+
156
+ return <UserProfile user={user} />;
157
+ }
158
+
159
+ // app/blog/[...slug]/page.tsx -- Catch-all route
160
+ interface BlogPageProps {
161
+ params: Promise<{ slug: string[] }>;
162
+ }
163
+
164
+ export default async function BlogPage({ params }: BlogPageProps) {
165
+ const { slug } = await params;
166
+ // slug = ["2024", "01", "my-post"] for /blog/2024/01/my-post
167
+ }
168
+ ```
169
+
170
+ ---
171
+
172
+ ## Environment Variables
173
+
174
+ ### Naming Convention
175
+
176
+ ```bash
177
+ # Server-only (never sent to browser)
178
+ DATABASE_URL="postgresql://..."
179
+ NEXTAUTH_SECRET="..."
180
+ STRIPE_SECRET_KEY="sk_..."
181
+ OPENAI_API_KEY="sk-..."
182
+
183
+ # Client-safe (bundled into browser code)
184
+ NEXT_PUBLIC_APP_NAME="MyApp"
185
+ NEXT_PUBLIC_APP_URL="https://myapp.com"
186
+ NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY="pk_..."
187
+ ```
188
+
189
+ ### Type-Safe Environment
190
+
191
+ ```typescript
192
+ // lib/env.ts
193
+ import { z } from "zod";
194
+
195
+ const envSchema = z.object({
196
+ DATABASE_URL: z.string().url(),
197
+ NEXTAUTH_SECRET: z.string().min(32),
198
+ NEXTAUTH_URL: z.string().url(),
199
+ NEXT_PUBLIC_APP_NAME: z.string(),
200
+ NEXT_PUBLIC_APP_URL: z.string().url(),
201
+ });
202
+
203
+ export const env = envSchema.parse(process.env);
204
+ ```
205
+
206
+ ### Files Hierarchy
207
+
208
+ | File | Purpose | Committed |
209
+ |---|---|---|
210
+ | `.env` | Defaults for all environments | Yes |
211
+ | `.env.local` | Local overrides with secrets | No (gitignored) |
212
+ | `.env.development` | Dev-specific defaults | Yes |
213
+ | `.env.production` | Prod-specific defaults | Yes |
214
+ | `.env.test` | Test environment | Yes |
215
+
216
+ ---
217
+
218
+ ## Git Workflow
219
+
220
+ ### Branch Naming
221
+
222
+ ```
223
+ feature/add-user-dashboard
224
+ fix/login-redirect-loop
225
+ chore/upgrade-prisma-6
226
+ refactor/extract-auth-middleware
227
+ ```
228
+
229
+ ### Commit Messages
230
+
231
+ ```
232
+ feat: add user profile page with avatar upload
233
+ fix: prevent duplicate form submissions on slow networks
234
+ refactor: extract validation schemas to shared module
235
+ chore: upgrade Next.js to 15.1
236
+ docs: add API authentication guide
237
+ test: add E2E tests for checkout flow
238
+ ```
239
+
240
+ ### Pull Request Checklist
241
+
242
+ - TypeScript compiles without errors (`pnpm tsc --noEmit`)
243
+ - ESLint passes (`pnpm lint`)
244
+ - Tests pass (`pnpm test`)
245
+ - New routes have `loading.tsx` and `error.tsx`
246
+ - Environment variables documented if added
247
+ - Database migrations included if schema changed
248
+
249
+ ---
250
+
251
+ ## Feature Flags
252
+
253
+ ### Simple Environment-Based
254
+
255
+ ```typescript
256
+ // lib/flags.ts
257
+ export const flags = {
258
+ newDashboard: process.env.NEXT_PUBLIC_FF_NEW_DASHBOARD === "true",
259
+ aiFeatures: process.env.FF_AI_FEATURES === "true",
260
+ } as const;
261
+ ```
262
+
263
+ ### Usage
264
+
265
+ ```typescript
266
+ import { flags } from "@/lib/flags";
267
+
268
+ export default function DashboardPage() {
269
+ if (flags.newDashboard) {
270
+ return <NewDashboard />;
271
+ }
272
+ return <LegacyDashboard />;
273
+ }
274
+ ```
275
+
276
+ ---
277
+
278
+ ## Metadata and SEO
279
+
280
+ ### Static Metadata
281
+
282
+ ```typescript
283
+ // app/layout.tsx
284
+ import type { Metadata } from "next";
285
+
286
+ export const metadata: Metadata = {
287
+ title: {
288
+ default: "MyApp",
289
+ template: "%s | MyApp",
290
+ },
291
+ description: "A modern web application",
292
+ metadataBase: new URL("https://myapp.com"),
293
+ openGraph: {
294
+ type: "website",
295
+ locale: "en_US",
296
+ siteName: "MyApp",
297
+ },
298
+ };
299
+ ```
300
+
301
+ ### Dynamic Metadata
302
+
303
+ ```typescript
304
+ // app/users/[id]/page.tsx
305
+ import type { Metadata } from "next";
306
+
307
+ export async function generateMetadata({ params }: PageProps): Promise<Metadata> {
308
+ const { id } = await params;
309
+ const user = await prisma.user.findUnique({ where: { id } });
310
+
311
+ return {
312
+ title: user?.name ?? "User Not Found",
313
+ description: user?.bio ?? undefined,
314
+ };
315
+ }
316
+ ```
317
+
318
+ ---
319
+
320
+ ## Anti-Patterns
321
+
322
+ | Anti-Pattern | Problem | Correct Approach |
323
+ |---|---|---|
324
+ | Custom routing library | Fights the framework | Use App Router file conventions |
325
+ | `NEXT_PUBLIC_` on secrets | Exposes secrets to the browser | Only prefix client-safe values |
326
+ | Missing `loading.tsx` | Blank screen during navigation | Add loading states for every route segment |
327
+ | Deeply nested routes (5+ levels) | Hard to navigate, slow resolution | Flatten with route groups |
328
+ | Environment variables without validation | Runtime crashes on missing values | Validate with zod at startup |
329
+ | Feature code in `app/` directory | Mixes routing with business logic | Keep route files thin, logic in `lib/` or `actions/` |
330
+
331
+ ---
332
+
333
+ _Follow the framework's conventions. When Next.js provides a file-based solution, use it instead of inventing your own._
@@ -0,0 +1,310 @@
1
+ # CSS and Styling
2
+
3
+ Tailwind CSS v4 patterns for Next.js projects with design tokens, component variants, and dark mode.
4
+
5
+ ---
6
+
7
+ ## Philosophy
8
+
9
+ - **Utility-first**: Compose styles in markup; extract components, not CSS classes
10
+ - **Design tokens**: Define colors, spacing, and typography as CSS custom properties
11
+ - **No custom CSS unless necessary**: Tailwind utilities handle 95% of cases
12
+ - **Consistent variants**: Use class-variance-authority for component style APIs
13
+
14
+ ---
15
+
16
+ ## Tailwind CSS v4 Configuration
17
+
18
+ ### CSS-First Config (No `tailwind.config.js`)
19
+
20
+ ```css
21
+ /* app/globals.css */
22
+ @import "tailwindcss";
23
+
24
+ @theme {
25
+ /* Colors */
26
+ --color-brand-50: oklch(0.97 0.02 250);
27
+ --color-brand-500: oklch(0.55 0.2 250);
28
+ --color-brand-900: oklch(0.25 0.1 250);
29
+
30
+ /* Semantic colors */
31
+ --color-background: var(--color-white);
32
+ --color-foreground: var(--color-gray-950);
33
+ --color-muted: var(--color-gray-100);
34
+ --color-muted-foreground: var(--color-gray-500);
35
+ --color-border: var(--color-gray-200);
36
+ --color-primary: var(--color-brand-500);
37
+ --color-destructive: oklch(0.55 0.2 25);
38
+
39
+ /* Typography */
40
+ --font-sans: "Inter Variable", ui-sans-serif, system-ui, sans-serif;
41
+ --font-mono: "JetBrains Mono Variable", ui-monospace, monospace;
42
+
43
+ /* Radius */
44
+ --radius-sm: 0.25rem;
45
+ --radius-md: 0.375rem;
46
+ --radius-lg: 0.5rem;
47
+
48
+ /* Shadows */
49
+ --shadow-sm: 0 1px 2px 0 oklch(0 0 0 / 0.05);
50
+ }
51
+ ```
52
+
53
+ ### Dark Mode
54
+
55
+ ```css
56
+ /* app/globals.css (continued) */
57
+ @variant dark (&:where(.dark, .dark *));
58
+
59
+ @theme {
60
+ /* Light mode defaults above, dark mode overrides: */
61
+ }
62
+
63
+ .dark {
64
+ --color-background: var(--color-gray-950);
65
+ --color-foreground: var(--color-gray-50);
66
+ --color-muted: var(--color-gray-900);
67
+ --color-muted-foreground: var(--color-gray-400);
68
+ --color-border: var(--color-gray-800);
69
+ }
70
+ ```
71
+
72
+ ### Dark Mode Toggle
73
+
74
+ ```typescript
75
+ // components/theme-toggle.tsx
76
+ "use client";
77
+
78
+ import { useEffect, useState } from "react";
79
+
80
+ export function ThemeToggle() {
81
+ const [dark, setDark] = useState(false);
82
+
83
+ useEffect(() => {
84
+ const stored = localStorage.getItem("theme");
85
+ const prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
86
+ setDark(stored === "dark" || (!stored && prefersDark));
87
+ }, []);
88
+
89
+ useEffect(() => {
90
+ document.documentElement.classList.toggle("dark", dark);
91
+ localStorage.setItem("theme", dark ? "dark" : "light");
92
+ }, [dark]);
93
+
94
+ return (
95
+ <button onClick={() => setDark(!dark)} aria-label="Toggle dark mode">
96
+ {dark ? <SunIcon /> : <MoonIcon />}
97
+ </button>
98
+ );
99
+ }
100
+ ```
101
+
102
+ ---
103
+
104
+ ## The `cn()` Utility
105
+
106
+ ### Setup
107
+
108
+ ```typescript
109
+ // lib/utils.ts
110
+ import { clsx, type ClassValue } from "clsx";
111
+ import { twMerge } from "tailwind-merge";
112
+
113
+ export function cn(...inputs: ClassValue[]) {
114
+ return twMerge(clsx(inputs));
115
+ }
116
+ ```
117
+
118
+ ### Usage
119
+
120
+ ```typescript
121
+ import { cn } from "@/lib/utils";
122
+
123
+ interface BadgeProps {
124
+ variant: "success" | "warning" | "error";
125
+ className?: string;
126
+ children: React.ReactNode;
127
+ }
128
+
129
+ export function Badge({ variant, className, children }: BadgeProps) {
130
+ return (
131
+ <span
132
+ className={cn(
133
+ "inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium",
134
+ {
135
+ "bg-green-100 text-green-800": variant === "success",
136
+ "bg-yellow-100 text-yellow-800": variant === "warning",
137
+ "bg-red-100 text-red-800": variant === "error",
138
+ },
139
+ className
140
+ )}
141
+ >
142
+ {children}
143
+ </span>
144
+ );
145
+ }
146
+ ```
147
+
148
+ **Why `cn()`**: `clsx` handles conditionals, `twMerge` resolves conflicting Tailwind classes (e.g., a passed `className` of `bg-blue-500` correctly overrides an internal `bg-green-100`).
149
+
150
+ ---
151
+
152
+ ## Component Variants with CVA
153
+
154
+ ### Setup
155
+
156
+ ```typescript
157
+ // components/ui/button.tsx
158
+ import { cva, type VariantProps } from "class-variance-authority";
159
+ import { cn } from "@/lib/utils";
160
+
161
+ const buttonVariants = cva(
162
+ // Base styles
163
+ "inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
164
+ {
165
+ variants: {
166
+ variant: {
167
+ primary: "bg-primary text-white hover:bg-primary/90",
168
+ secondary: "bg-muted text-foreground hover:bg-muted/80",
169
+ destructive: "bg-destructive text-white hover:bg-destructive/90",
170
+ outline: "border border-border bg-transparent hover:bg-muted",
171
+ ghost: "hover:bg-muted",
172
+ link: "text-primary underline-offset-4 hover:underline",
173
+ },
174
+ size: {
175
+ sm: "h-8 px-3 text-xs",
176
+ md: "h-10 px-4",
177
+ lg: "h-12 px-6 text-base",
178
+ icon: "h-10 w-10",
179
+ },
180
+ },
181
+ defaultVariants: {
182
+ variant: "primary",
183
+ size: "md",
184
+ },
185
+ }
186
+ );
187
+
188
+ interface ButtonProps
189
+ extends React.ComponentProps<"button">,
190
+ VariantProps<typeof buttonVariants> {
191
+ isLoading?: boolean;
192
+ }
193
+
194
+ export function Button({
195
+ variant,
196
+ size,
197
+ isLoading,
198
+ disabled,
199
+ className,
200
+ children,
201
+ ...props
202
+ }: ButtonProps) {
203
+ return (
204
+ <button
205
+ className={cn(buttonVariants({ variant, size }), className)}
206
+ disabled={disabled || isLoading}
207
+ {...props}
208
+ >
209
+ {isLoading && <Spinner className="mr-2 h-4 w-4 animate-spin" />}
210
+ {children}
211
+ </button>
212
+ );
213
+ }
214
+
215
+ export { buttonVariants };
216
+ ```
217
+
218
+ ---
219
+
220
+ ## Common Patterns
221
+
222
+ ### Container
223
+
224
+ ```css
225
+ /* Use Tailwind's container or define a custom one */
226
+ @utility container {
227
+ margin-inline: auto;
228
+ padding-inline: 1rem;
229
+ max-width: 80rem;
230
+ }
231
+ ```
232
+
233
+ ### Focus Styles
234
+
235
+ ```typescript
236
+ // Consistent focus ring across all interactive elements
237
+ const focusRing = "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2";
238
+ ```
239
+
240
+ ### Animations
241
+
242
+ ```css
243
+ /* app/globals.css */
244
+ @theme {
245
+ --animate-fade-in: fade-in 0.2s ease-out;
246
+ --animate-slide-up: slide-up 0.3s ease-out;
247
+ }
248
+
249
+ @keyframes fade-in {
250
+ from { opacity: 0; }
251
+ to { opacity: 1; }
252
+ }
253
+
254
+ @keyframes slide-up {
255
+ from { opacity: 0; transform: translateY(8px); }
256
+ to { opacity: 1; transform: translateY(0); }
257
+ }
258
+ ```
259
+
260
+ ---
261
+
262
+ ## Layout Patterns
263
+
264
+ ### Sidebar Layout
265
+
266
+ ```typescript
267
+ export function DashboardLayout({ children }: { children: React.ReactNode }) {
268
+ return (
269
+ <div className="flex min-h-screen">
270
+ <aside className="w-64 shrink-0 border-r bg-muted/50">
271
+ <nav className="p-4">
272
+ {/* nav items */}
273
+ </nav>
274
+ </aside>
275
+ <main className="flex-1 p-8">{children}</main>
276
+ </div>
277
+ );
278
+ }
279
+ ```
280
+
281
+ ### Centered Content
282
+
283
+ ```typescript
284
+ export function AuthLayout({ children }: { children: React.ReactNode }) {
285
+ return (
286
+ <div className="flex min-h-screen items-center justify-center bg-muted/30">
287
+ <div className="w-full max-w-md rounded-xl border bg-background p-8 shadow-sm">
288
+ {children}
289
+ </div>
290
+ </div>
291
+ );
292
+ }
293
+ ```
294
+
295
+ ---
296
+
297
+ ## Anti-Patterns
298
+
299
+ | Anti-Pattern | Problem | Correct Approach |
300
+ |---|---|---|
301
+ | `@apply` everywhere | Defeats utility-first purpose | Extract React components instead |
302
+ | Hardcoded colors | Inconsistent, no dark mode | Use design tokens from `@theme` |
303
+ | `!important` in utilities | Fragile, hard to override | Use `cn()` to merge conflicts |
304
+ | CSS modules + Tailwind | Two styling systems to maintain | Pick one; prefer Tailwind |
305
+ | Inline `style={{}}` | No design system, no responsive | Use Tailwind utilities |
306
+ | Magic numbers for spacing | Inconsistent spacing | Use Tailwind's spacing scale |
307
+
308
+ ---
309
+
310
+ _Style components with utilities. Extract React components, not CSS classes. Let the design system enforce consistency._