vibes-plug 2.5.0 → 2.11.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 (177) hide show
  1. package/.claude/rules/vibes-plug-core.md +32 -0
  2. package/.cursor/rules/vibes-plug-core.mdc +51 -0
  3. package/.cursorrules +42 -0
  4. package/AGENTS.md +37 -7
  5. package/BLUEPRINT.md +309 -217
  6. package/CHANGELOG.md +133 -1
  7. package/CLAUDE.md +70 -0
  8. package/LICENSE +1 -1
  9. package/README.md +641 -415
  10. package/index.js +19 -0
  11. package/package.json +44 -8
  12. package/plugin.json +24 -7
  13. package/scripts/generate_swarm_gif.py +295 -0
  14. package/scripts/install.js +201 -0
  15. package/skills/accessibility-testing-expert/SKILL.md +116 -0
  16. package/skills/ai-cost-token-optimizer/SKILL.md +82 -52
  17. package/skills/ai-evals-benchmark-expert/SKILL.md +188 -0
  18. package/skills/ai-llm-integration-expert/SKILL.md +185 -178
  19. package/skills/ai-media-generation-expert/SKILL.md +172 -0
  20. package/skills/ai-prompt-engineering-expert/SKILL.md +84 -0
  21. package/skills/angular-expert/SKILL.md +148 -0
  22. package/skills/api-design-expert/SKILL.md +6 -3
  23. package/skills/api-gateway-proxy-expert/SKILL.md +81 -0
  24. package/skills/app-analyzer-optimizer/SKILL.md +6 -3
  25. package/skills/apple-ecosystem-expert/SKILL.md +144 -141
  26. package/skills/{asisten_ramah → asisten-ramah}/SKILL.md +7 -1
  27. package/skills/astro-framework-expert/SKILL.md +200 -0
  28. package/skills/async-queue-temporal-expert/SKILL.md +210 -24
  29. package/skills/authentication-identity-expert/SKILL.md +278 -275
  30. package/skills/auto-doc-updater/SKILL.md +7 -1
  31. package/skills/autonomous-chaos-monkey/SKILL.md +63 -63
  32. package/skills/autonomous-red-teamer/SKILL.md +172 -28
  33. package/skills/autonomous-tdd-debugger/SKILL.md +70 -64
  34. package/skills/background-jobs-queue-expert/SKILL.md +235 -0
  35. package/skills/biome-linter-formatter-expert/SKILL.md +89 -0
  36. package/skills/blockchain-web3-expert/SKILL.md +115 -0
  37. package/skills/bootstrap-to-modern/SKILL.md +9 -6
  38. package/skills/brainstorming/SKILL.md +58 -50
  39. package/skills/browser-automation-expert/SKILL.md +197 -21
  40. package/skills/bun-runtime-expert/SKILL.md +7 -1
  41. package/skills/chatbot-messaging-expert/SKILL.md +114 -0
  42. package/skills/ci-cd-devops-architect/SKILL.md +45 -36
  43. package/skills/cloud-hosting-expert/SKILL.md +7 -1
  44. package/skills/coderabbit/SKILL.md +7 -1
  45. package/skills/compliance-gdpr-privacy-expert/SKILL.md +85 -0
  46. package/skills/cron-scheduler-expert/SKILL.md +303 -297
  47. package/skills/data-pipeline-etl-expert/SKILL.md +84 -0
  48. package/skills/data-telemetry-expert/SKILL.md +7 -1
  49. package/skills/data-visualization-expert/SKILL.md +154 -0
  50. package/skills/database-migration-versioning-expert/SKILL.md +90 -0
  51. package/skills/database-orm-expert/SKILL.md +13 -3
  52. package/skills/dependency-upgrade-migrator/SKILL.md +300 -294
  53. package/skills/design-system-architect/SKILL.md +278 -259
  54. package/skills/desktop-electron-expert/SKILL.md +128 -0
  55. package/skills/documentation-site-expert/SKILL.md +59 -0
  56. package/skills/doku-mcp-server/SKILL.md +7 -1
  57. package/skills/doku-payment-gateway/SKILL.md +7 -1
  58. package/skills/domain-driven-design-expert/SKILL.md +82 -0
  59. package/skills/e2e-testing-expert/SKILL.md +7 -1
  60. package/skills/ecommerce-expert/SKILL.md +87 -0
  61. package/skills/edge-serverless-db-expert/SKILL.md +98 -42
  62. package/skills/email-notification-expert/SKILL.md +367 -361
  63. package/skills/error-resilience-expert/SKILL.md +485 -479
  64. package/skills/event-driven-architect/SKILL.md +7 -1
  65. package/skills/feature-flag-analytics-expert/SKILL.md +65 -45
  66. package/skills/file-upload-media-expert/SKILL.md +436 -430
  67. package/skills/firebase-security-expert/SKILL.md +7 -1
  68. package/skills/form-validation-expert/SKILL.md +406 -400
  69. package/skills/fullstack-expert/SKILL.md +60 -1
  70. package/skills/gemini-agent-booster/SKILL.md +173 -135
  71. package/skills/geospatial-maps-expert/SKILL.md +80 -0
  72. package/skills/global-a11y-i18n-expert/SKILL.md +7 -1
  73. package/skills/glsl-shader-expert/SKILL.md +106 -100
  74. package/skills/go-programming-expert/SKILL.md +21 -15
  75. package/skills/graph-rag-knowledge-expert/SKILL.md +159 -0
  76. package/skills/graphql-apollo-expert/SKILL.md +113 -107
  77. package/skills/headless-cms-expert/SKILL.md +181 -0
  78. package/skills/hig/SKILL.md +7 -1
  79. package/skills/js-backend-expert/SKILL.md +218 -216
  80. package/skills/legacy-code-translator/SKILL.md +70 -64
  81. package/skills/local-slm-edge-ai-expert/SKILL.md +167 -0
  82. package/skills/logging-error-tracking-expert/SKILL.md +343 -337
  83. package/skills/mcp-client-orchestrator/SKILL.md +75 -69
  84. package/skills/mcp-server-architect/SKILL.md +294 -194
  85. package/skills/micro-frontend-architect/SKILL.md +111 -105
  86. package/skills/mobile-expo-expert/SKILL.md +8 -2
  87. package/skills/mobile-push-notification-expert/SKILL.md +70 -50
  88. package/skills/modern-css-native-expert/SKILL.md +189 -0
  89. package/skills/monday-design-aesthetic/SKILL.md +7 -1
  90. package/skills/monorepo-architect/SKILL.md +7 -1
  91. package/skills/mpa-orchestrator/SKILL.md +20 -1
  92. package/skills/multi-agent-orchestration/SKILL.md +254 -234
  93. package/skills/multiple-entry-points/SKILL.md +37 -1
  94. package/skills/mvc-expert/SKILL.md +7 -1
  95. package/skills/n8n-automation-expert/SKILL.md +89 -0
  96. package/skills/nextjs-app-router-expert/SKILL.md +148 -0
  97. package/skills/openapi-swagger-codegen-expert/SKILL.md +67 -0
  98. package/skills/payment-gateway-expert/SKILL.md +85 -1
  99. package/skills/pdf-document-generation-expert/SKILL.md +91 -0
  100. package/skills/performance-web-vitals/SKILL.md +7 -1
  101. package/skills/post-quantum-crypto-migrator/SKILL.md +57 -57
  102. package/skills/prd-architect/SKILL.md +7 -1
  103. package/skills/proactive-background-watcher/SKILL.md +67 -61
  104. package/skills/production-ready-hardener/SKILL.md +461 -455
  105. package/skills/project-context-mapper/SKILL.md +84 -78
  106. package/skills/pwa-offline-first-expert/SKILL.md +185 -0
  107. package/skills/python-programming-expert/SKILL.md +407 -401
  108. package/skills/rate-limit-abuse-prevention/SKILL.md +376 -370
  109. package/skills/realtime-collaboration-expert/SKILL.md +55 -1
  110. package/skills/rich-text-editor-expert/SKILL.md +177 -0
  111. package/skills/rust-programming-expert/SKILL.md +7 -1
  112. package/skills/saas-billing/SKILL.md +7 -1
  113. package/skills/saas-multi-tenant/SKILL.md +7 -1
  114. package/skills/saas-mvp-launcher/SKILL.md +20 -1
  115. package/skills/saas-transformer/SKILL.md +499 -488
  116. package/skills/scalability-clean-code/SKILL.md +7 -1
  117. package/skills/search-engine-expert/SKILL.md +89 -0
  118. package/skills/secure-fuzz-testing/SKILL.md +7 -1
  119. package/skills/self-evolving-memory-graph/SKILL.md +90 -74
  120. package/skills/self-healing-cloud-orchestrator/SKILL.md +57 -57
  121. package/skills/senior-frontend/SKILL.md +141 -161
  122. package/skills/seo/SKILL.md +41 -17
  123. package/skills/session-context-loader/SKILL.md +82 -76
  124. package/skills/session-handoff-resume/SKILL.md +7 -1
  125. package/skills/{skill_baru → skill-baru}/SKILL.md +8 -2
  126. package/skills/solidjs-expert/SKILL.md +80 -0
  127. package/skills/spa-orchestrator/SKILL.md +20 -1
  128. package/skills/sse-websocket-streaming-expert/SKILL.md +93 -0
  129. package/skills/state-management-expert/SKILL.md +7 -1
  130. package/skills/supabase-migration/SKILL.md +47 -1
  131. package/skills/supabase-security-expert/SKILL.md +7 -1
  132. package/skills/svelte-sveltekit-expert/SKILL.md +91 -0
  133. package/skills/svg-animation-motion-expert/SKILL.md +115 -0
  134. package/skills/tailwind-expert/SKILL.md +88 -136
  135. package/skills/tanstack-query-expert/SKILL.md +7 -1
  136. package/skills/tauri-expert/SKILL.md +7 -1
  137. package/skills/token-saver/SKILL.md +1 -1
  138. package/skills/typescript-expert/SKILL.md +12 -6
  139. package/skills/ui-components-expert/SKILL.md +165 -279
  140. package/skills/ui-ux-pro-max/SKILL.md +23 -3
  141. package/skills/vector-db-rag-expert/SKILL.md +175 -19
  142. package/skills/vibe-code-gardener/SKILL.md +1 -1
  143. package/skills/visual-qa-vision-agent/SKILL.md +70 -64
  144. package/skills/voice-ai-realtime-agent/SKILL.md +202 -0
  145. package/skills/vue-frontend-expert/SKILL.md +131 -125
  146. package/skills/wasm-edge-computing-expert/SKILL.md +97 -0
  147. package/skills/web-3d-graphics-expert/SKILL.md +136 -130
  148. package/skills/web-game-engine-expert/SKILL.md +101 -95
  149. package/skills/web-scraper/SKILL.md +157 -207
  150. package/skills/website-design-cloner/SKILL.md +179 -173
  151. package/skills/webxr-ar-vr-expert/SKILL.md +122 -116
  152. package/skills/wordpress-headless-expert/SKILL.md +144 -0
  153. package/skills/zero-to-prod-orchestrator/SKILL.md +52 -27
  154. package/skills/zero-trust-secret-vault/SKILL.md +87 -39
  155. package/.github/ISSUE_TEMPLATE/feature_request.md +0 -20
  156. package/.github/workflows/publish.yml +0 -20
  157. package/CONTRIBUTING.md +0 -199
  158. package/SECURITY.md +0 -21
  159. package/banner.png +0 -0
  160. package/skills/autonomous-swarm-director/SKILL.md +0 -69
  161. package/skills/hyper-context-synthesizer/SKILL.md +0 -55
  162. package/skills/llm-cost-arbitrage-router/SKILL.md +0 -59
  163. package/skills/senior-fullstack/SKILL.md +0 -167
  164. package/skills/senior-fullstack/references/architecture_patterns.md +0 -160
  165. package/skills/senior-fullstack/references/development_workflows.md +0 -222
  166. package/skills/senior-fullstack/references/tech_stack_guide.md +0 -190
  167. package/skills/senior-fullstack/scripts/code_quality_analyzer.py +0 -114
  168. package/skills/senior-fullstack/scripts/fullstack_scaffolder.py +0 -114
  169. package/skills/senior-fullstack/scripts/project_scaffolder.py +0 -114
  170. package/skills/seo-aeo-landing-page-writer/SKILL.md +0 -97
  171. package/skills/seo-geo/SKILL.md +0 -188
  172. package/skills/ui-ux-pro-max/scripts/__pycache__/core.cpython-310.pyc +0 -0
  173. package/skills/ui-ux-pro-max/scripts/__pycache__/core.cpython-312.pyc +0 -0
  174. package/skills/ui-ux-pro-max/scripts/__pycache__/design_system.cpython-310.pyc +0 -0
  175. package/skills/ui-ux-pro-max/scripts/__pycache__/design_system.cpython-312.pyc +0 -0
  176. package/skills/ui_ux_expert/SKILL.md +0 -125
  177. package/vibes-swarm-demo.gif +0 -0
@@ -1,401 +1,407 @@
1
- ---
2
- name: form-validation-expert
3
- description: "Expert guide for complex form handling with React Hook Form, server-side validation (useActionState + Zod), multi-step wizards, and accessible form patterns / Panduan ahli penanganan formulir kompleks dengan React Hook Form, validasi server-side, wizard multi-langkah, dan pola formulir aksesibel."
1
+ ---
2
+ name: form-validation-expert
3
+ description: "Expert guide for complex form handling with React Hook Form, server-side validation (useActionState + Zod), multi-step wizards, and accessible form patterns / Panduan ahli penanganan formulir kompleks dengan React Hook Form, validasi server-side, wizard multi-langkah, dan pola formulir aksesibel."
4
4
  author: "Roedy Rustam"
5
- ---
6
-
7
- # Form & Validation Expert (2026 Edition)
8
-
9
- [English](#english) | [Bahasa Indonesia](#bahasa-indonesia)
10
-
11
- ---
12
-
13
- <a name="english"></a>
14
- ## English
15
-
16
- ### Description
17
- Production-grade guide for building robust, accessible, and user-friendly forms. Covers **React Hook Form v7+** with Zod/Valibot resolvers, **server-side validation** with `useActionState` + Zod, **multi-step form wizards**, **dynamic forms** from JSON Schema, **autofill-friendly patterns**, **optimistic form submission** with `useOptimistic`, **Conform** (progressive enhancement), and **form accessibility** (ARIA, error announcements).
18
-
19
- ### Trigger Conditions
20
- Activate this skill when:
21
- - Building forms with client-side validation (React Hook Form, Zod, Valibot).
22
- - Implementing server-side form validation with React 19 Server Actions.
23
- - Creating multi-step form wizards with state persistence.
24
- - Building dynamic forms generated from schema definitions.
25
- - Making forms accessible (ARIA attributes, error announcements).
26
- - Implementing file upload forms with drag-and-drop.
27
- - Optimizing forms for autofill and mobile usability.
28
-
29
- ---
30
-
31
- ### Form Library Selection Guide
32
-
33
- | Library | Best For | Key Strength |
34
- |---|---|---|
35
- | **React Hook Form + Zod** | Most React apps | Performance (uncontrolled), rich ecosystem |
36
- | **Conform** | Progressive enhancement, RSC | Works without JS, native validation |
37
- | **Formik** | Legacy projects | Mature, large community (declining) |
38
- | **TanStack Form** | Complex, headless forms | Framework-agnostic, type-safe |
39
-
40
- **Recommendation**: Use **React Hook Form + Zod** for most projects. Use **Conform** for Next.js Server Actions with progressive enhancement.
41
-
42
- ---
43
-
44
- ### 1. React Hook Form + Zod (Client-Side)
45
-
46
- ```tsx
47
- // components/signup-form.tsx
48
- 'use client';
49
-
50
- import { useForm } from 'react-hook-form';
51
- import { zodResolver } from '@hookform/resolvers/zod';
52
- import { z } from 'zod';
53
-
54
- const signupSchema = z.object({
55
- name: z.string().min(2, 'Name must be at least 2 characters'),
56
- email: z.string().email('Please enter a valid email'),
57
- password: z.string()
58
- .min(8, 'Password must be at least 8 characters')
59
- .regex(/[A-Z]/, 'Must contain an uppercase letter')
60
- .regex(/[0-9]/, 'Must contain a number'),
61
- confirmPassword: z.string(),
62
- }).refine(data => data.password === data.confirmPassword, {
63
- message: 'Passwords do not match',
64
- path: ['confirmPassword'],
65
- });
66
-
67
- type SignupFormData = z.infer<typeof signupSchema>;
68
-
69
- export function SignupForm() {
70
- const {
71
- register,
72
- handleSubmit,
73
- formState: { errors, isSubmitting },
74
- } = useForm<SignupFormData>({
75
- resolver: zodResolver(signupSchema),
76
- mode: 'onBlur', // Validate on blur for better UX
77
- });
78
-
79
- const onSubmit = async (data: SignupFormData) => {
80
- const res = await fetch('/api/auth/signup', {
81
- method: 'POST',
82
- headers: { 'Content-Type': 'application/json' },
83
- body: JSON.stringify(data),
84
- });
85
- if (!res.ok) throw new Error('Signup failed');
86
- };
87
-
88
- return (
89
- <form onSubmit={handleSubmit(onSubmit)} noValidate>
90
- <div>
91
- <label htmlFor="name">Name</label>
92
- <input
93
- id="name"
94
- {...register('name')}
95
- aria-invalid={!!errors.name}
96
- aria-describedby={errors.name ? 'name-error' : undefined}
97
- autoComplete="name"
98
- />
99
- {errors.name && <p id="name-error" role="alert">{errors.name.message}</p>}
100
- </div>
101
-
102
- <div>
103
- <label htmlFor="email">Email</label>
104
- <input
105
- id="email"
106
- type="email"
107
- {...register('email')}
108
- aria-invalid={!!errors.email}
109
- aria-describedby={errors.email ? 'email-error' : undefined}
110
- autoComplete="email"
111
- />
112
- {errors.email && <p id="email-error" role="alert">{errors.email.message}</p>}
113
- </div>
114
-
115
- <div>
116
- <label htmlFor="password">Password</label>
117
- <input
118
- id="password"
119
- type="password"
120
- {...register('password')}
121
- aria-invalid={!!errors.password}
122
- aria-describedby={errors.password ? 'password-error' : undefined}
123
- autoComplete="new-password"
124
- />
125
- {errors.password && <p id="password-error" role="alert">{errors.password.message}</p>}
126
- </div>
127
-
128
- <div>
129
- <label htmlFor="confirmPassword">Confirm Password</label>
130
- <input
131
- id="confirmPassword"
132
- type="password"
133
- {...register('confirmPassword')}
134
- aria-invalid={!!errors.confirmPassword}
135
- aria-describedby={errors.confirmPassword ? 'confirm-error' : undefined}
136
- autoComplete="new-password"
137
- />
138
- {errors.confirmPassword && <p id="confirm-error" role="alert">{errors.confirmPassword.message}</p>}
139
- </div>
140
-
141
- <button type="submit" disabled={isSubmitting}>
142
- {isSubmitting ? 'Creating account...' : 'Sign Up'}
143
- </button>
144
- </form>
145
- );
146
- }
147
- ```
148
-
149
- ---
150
-
151
- ### 2. Server Actions + useActionState (React 19 / Next.js 15)
152
-
153
- ```typescript
154
- // actions/signup.ts
155
- 'use server';
156
-
157
- import { z } from 'zod';
158
-
159
- const signupSchema = z.object({
160
- name: z.string().min(2),
161
- email: z.string().email(),
162
- password: z.string().min(8),
163
- });
164
-
165
- export type SignupState = {
166
- errors?: Record<string, string[]>;
167
- message?: string;
168
- success?: boolean;
169
- };
170
-
171
- export async function signupAction(
172
- prevState: SignupState,
173
- formData: FormData,
174
- ): Promise<SignupState> {
175
- const parsed = signupSchema.safeParse({
176
- name: formData.get('name'),
177
- email: formData.get('email'),
178
- password: formData.get('password'),
179
- });
180
-
181
- if (!parsed.success) {
182
- return {
183
- errors: parsed.error.flatten().fieldErrors as Record<string, string[]>,
184
- message: 'Please fix the errors below.',
185
- };
186
- }
187
-
188
- // Check if email already exists
189
- const existing = await db.query.users.findFirst({
190
- where: eq(users.email, parsed.data.email),
191
- });
192
- if (existing) {
193
- return { errors: { email: ['This email is already registered'] } };
194
- }
195
-
196
- // Create user
197
- await createUser(parsed.data);
198
- return { success: true, message: 'Account created!' };
199
- }
200
- ```
201
-
202
- ```tsx
203
- // components/signup-form-server.tsx
204
- 'use client';
205
-
206
- import { useActionState } from 'react';
207
- import { signupAction, type SignupState } from '@/actions/signup';
208
-
209
- export function SignupFormServer() {
210
- const [state, formAction, isPending] = useActionState<SignupState, FormData>(
211
- signupAction,
212
- {},
213
- );
214
-
215
- return (
216
- <form action={formAction}>
217
- {state.message && (
218
- <div role="alert" className={state.success ? 'success' : 'error'}>
219
- {state.message}
220
- </div>
221
- )}
222
-
223
- <div>
224
- <label htmlFor="name">Name</label>
225
- <input id="name" name="name" required autoComplete="name" />
226
- {state.errors?.name && <p role="alert">{state.errors.name[0]}</p>}
227
- </div>
228
-
229
- <div>
230
- <label htmlFor="email">Email</label>
231
- <input id="email" name="email" type="email" required autoComplete="email" />
232
- {state.errors?.email && <p role="alert">{state.errors.email[0]}</p>}
233
- </div>
234
-
235
- <div>
236
- <label htmlFor="password">Password</label>
237
- <input id="password" name="password" type="password" required minLength={8} autoComplete="new-password" />
238
- {state.errors?.password && <p role="alert">{state.errors.password[0]}</p>}
239
- </div>
240
-
241
- <button type="submit" disabled={isPending}>
242
- {isPending ? 'Creating...' : 'Sign Up'}
243
- </button>
244
- </form>
245
- );
246
- }
247
- ```
248
-
249
- ---
250
-
251
- ### 3. Multi-Step Form Wizard
252
-
253
- ```tsx
254
- // components/multi-step-form.tsx
255
- 'use client';
256
-
257
- import { useState, useCallback } from 'react';
258
- import { useForm, FormProvider, type UseFormReturn } from 'react-hook-form';
259
- import { zodResolver } from '@hookform/resolvers/zod';
260
- import { z } from 'zod';
261
-
262
- // Step schemas
263
- const step1Schema = z.object({
264
- companyName: z.string().min(1, 'Required'),
265
- industry: z.string().min(1, 'Required'),
266
- });
267
-
268
- const step2Schema = z.object({
269
- contactName: z.string().min(1, 'Required'),
270
- contactEmail: z.string().email(),
271
- });
272
-
273
- const step3Schema = z.object({
274
- plan: z.enum(['starter', 'pro', 'enterprise']),
275
- billingCycle: z.enum(['monthly', 'annual']),
276
- });
277
-
278
- const fullSchema = step1Schema.merge(step2Schema).merge(step3Schema);
279
- type FormData = z.infer<typeof fullSchema>;
280
-
281
- const STEP_SCHEMAS = [step1Schema, step2Schema, step3Schema] as const;
282
- const STEP_TITLES = ['Company Info', 'Contact Details', 'Choose Plan'] as const;
283
-
284
- export function MultiStepForm() {
285
- const [currentStep, setCurrentStep] = useState(0);
286
- const methods = useForm<FormData>({
287
- resolver: zodResolver(fullSchema),
288
- mode: 'onChange',
289
- });
290
-
291
- const goNext = useCallback(async () => {
292
- const schema = STEP_SCHEMAS[currentStep];
293
- const fields = Object.keys(schema.shape) as (keyof FormData)[];
294
- const isValid = await methods.trigger(fields);
295
- if (isValid) setCurrentStep(s => Math.min(s + 1, STEP_SCHEMAS.length - 1));
296
- }, [currentStep, methods]);
297
-
298
- const goBack = useCallback(() => {
299
- setCurrentStep(s => Math.max(s - 1, 0));
300
- }, []);
301
-
302
- const onSubmit = async (data: FormData) => {
303
- await fetch('/api/onboarding', {
304
- method: 'POST',
305
- body: JSON.stringify(data),
306
- });
307
- };
308
-
309
- return (
310
- <FormProvider {...methods}>
311
- {/* Progress indicator */}
312
- <nav aria-label="Form progress">
313
- <ol className="step-progress">
314
- {STEP_TITLES.map((title, i) => (
315
- <li key={i} className={i === currentStep ? 'active' : i < currentStep ? 'completed' : ''}>
316
- {title}
317
- </li>
318
- ))}
319
- </ol>
320
- </nav>
321
-
322
- <form onSubmit={methods.handleSubmit(onSubmit)}>
323
- {currentStep === 0 && <Step1 />}
324
- {currentStep === 1 && <Step2 />}
325
- {currentStep === 2 && <Step3 />}
326
-
327
- <div className="button-group">
328
- {currentStep > 0 && <button type="button" onClick={goBack}>Back</button>}
329
- {currentStep < STEP_SCHEMAS.length - 1
330
- ? <button type="button" onClick={goNext}>Next</button>
331
- : <button type="submit">Submit</button>
332
- }
333
- </div>
334
- </form>
335
- </FormProvider>
336
- );
337
- }
338
- ```
339
-
340
- ---
341
-
342
- ### 4. Form Accessibility Checklist
343
-
344
- | Requirement | Implementation |
345
- |---|---|
346
- | **Labels** | Every input has a visible `<label>` with `htmlFor` matching input `id` |
347
- | **Error identification** | `aria-invalid="true"` on invalid fields |
348
- | **Error description** | `aria-describedby` pointing to error message element |
349
- | **Error announcement** | Error messages use `role="alert"` for screen readers |
350
- | **Required fields** | Use `aria-required="true"` or native `required` attribute |
351
- | **Autofill** | Correct `autoComplete` values (`name`, `email`, `new-password`) |
352
- | **Focus management** | Focus moves to first error on submit failure |
353
- | **Submit state** | Button shows loading state and is disabled during submission |
354
-
355
- ---
356
-
357
- ### Common Pitfalls to Avoid
358
-
359
- | Anti-Pattern | Problem | Correct Approach |
360
- |---|---|---|
361
- | Validating only on submit | Late error discovery, poor UX | Validate `onBlur` or `onChange` |
362
- | Client-side validation only | Can be bypassed | Always validate server-side too |
363
- | No `autoComplete` attributes | Misses browser autofill | Add correct `autoComplete` values |
364
- | Generic error messages | "Invalid field" doesn't help | Specific: "Must be at least 8 characters" |
365
- | Resetting form on error | User loses all input | Preserve input, highlight errors |
366
- | No loading state on submit | User clicks multiple times | Disable button + show spinner |
367
-
368
- ---
369
-
370
- ### Integration with Other Skills
371
-
372
- - `senior-frontend` — React 19 form patterns, useActionState, useOptimistic
373
- - `global-a11y-i18n-expert` WCAG 2.2 form accessibility requirements
374
- - `ui-components-expert` — Input control primitives, form field components
375
- - `authentication-identity-expert` — Login/signup form patterns
376
- - `error-resilience-expert` — Form submission error handling and retry
377
- - `tailwind-expert` — Form styling with Tailwind CSS v4
378
-
379
- ---
380
-
381
- <a name="bahasa-indonesia"></a>
382
- ## Bahasa Indonesia
383
-
384
- ### Deskripsi
385
- Panduan tingkat produksi untuk membangun formulir yang kuat, aksesibel, dan ramah pengguna. Mencakup **React Hook Form v7+** dengan Zod/Valibot resolver, **validasi server-side** dengan `useActionState` + Zod, **wizard formulir multi-langkah**, **formulir dinamis** dari JSON Schema, **pola autofill-friendly**, **optimistic form submission**, **Conform** (progressive enhancement), dan **aksesibilitas formulir** (ARIA, pengumuman error).
386
-
387
- ### Kondisi Pemicu
388
- Aktifkan skill ini ketika:
389
- - Membangun formulir dengan validasi client-side (React Hook Form, Zod, Valibot).
390
- - Mengimplementasikan validasi formulir server-side dengan React 19 Server Actions.
391
- - Membuat wizard formulir multi-langkah dengan persistensi state.
392
- - Membangun formulir dinamis dari definisi skema.
393
- - Membuat formulir yang aksesibel (atribut ARIA, pengumuman error).
394
-
395
- ### Integrasi dengan Skill Lain
396
-
397
- - `senior-frontend` Pola formulir React 19, useActionState, useOptimistic
398
- - `global-a11y-i18n-expert` Persyaratan aksesibilitas formulir WCAG 2.2
399
- - `ui-components-expert` Primitif kontrol input, komponen field formulir
400
- - `authentication-identity-expert` — Pola formulir login/signup
401
- - `error-resilience-expert` Penanganan error submission formulir dan retry
5
+ ---
6
+
7
+ # Form & Validation Expert (2026 Edition)
8
+
9
+ [English](#english) | [Bahasa Indonesia](#bahasa-indonesia)
10
+
11
+ ---
12
+
13
+ <a name="english"></a>
14
+ ## English
15
+
16
+ ### Orchestration & Integration
17
+ Connects and orchestrates with relevant domain skills like `brainstorming`, `zero-to-prod-orchestrator`, and `project-context-mapper` to ensure cohesive execution.
18
+
19
+ ### Description
20
+ Production-grade guide for building robust, accessible, and user-friendly forms. Covers **React Hook Form v7+** with Zod/Valibot resolvers, **server-side validation** with `useActionState` + Zod, **multi-step form wizards**, **dynamic forms** from JSON Schema, **autofill-friendly patterns**, **optimistic form submission** with `useOptimistic`, **Conform** (progressive enhancement), and **form accessibility** (ARIA, error announcements).
21
+
22
+ ### Trigger Conditions
23
+ Activate this skill when:
24
+ - Building forms with client-side validation (React Hook Form, Zod, Valibot).
25
+ - Implementing server-side form validation with React 19 Server Actions.
26
+ - Creating multi-step form wizards with state persistence.
27
+ - Building dynamic forms generated from schema definitions.
28
+ - Making forms accessible (ARIA attributes, error announcements).
29
+ - Implementing file upload forms with drag-and-drop.
30
+ - Optimizing forms for autofill and mobile usability.
31
+
32
+ ---
33
+
34
+ ### Form Library Selection Guide
35
+
36
+ | Library | Best For | Key Strength |
37
+ |---|---|---|
38
+ | **React Hook Form + Zod** | Most React apps | Performance (uncontrolled), rich ecosystem |
39
+ | **Conform** | Progressive enhancement, RSC | Works without JS, native validation |
40
+ | **Formik** | Legacy projects | Mature, large community (declining) |
41
+ | **TanStack Form** | Complex, headless forms | Framework-agnostic, type-safe |
42
+
43
+ **Recommendation**: Use **React Hook Form + Zod** for most projects. Use **Conform** for Next.js Server Actions with progressive enhancement.
44
+
45
+ ---
46
+
47
+ ### 1. React Hook Form + Zod (Client-Side)
48
+
49
+ ```tsx
50
+ // components/signup-form.tsx
51
+ 'use client';
52
+
53
+ import { useForm } from 'react-hook-form';
54
+ import { zodResolver } from '@hookform/resolvers/zod';
55
+ import { z } from 'zod';
56
+
57
+ const signupSchema = z.object({
58
+ name: z.string().min(2, 'Name must be at least 2 characters'),
59
+ email: z.string().email('Please enter a valid email'),
60
+ password: z.string()
61
+ .min(8, 'Password must be at least 8 characters')
62
+ .regex(/[A-Z]/, 'Must contain an uppercase letter')
63
+ .regex(/[0-9]/, 'Must contain a number'),
64
+ confirmPassword: z.string(),
65
+ }).refine(data => data.password === data.confirmPassword, {
66
+ message: 'Passwords do not match',
67
+ path: ['confirmPassword'],
68
+ });
69
+
70
+ type SignupFormData = z.infer<typeof signupSchema>;
71
+
72
+ export function SignupForm() {
73
+ const {
74
+ register,
75
+ handleSubmit,
76
+ formState: { errors, isSubmitting },
77
+ } = useForm<SignupFormData>({
78
+ resolver: zodResolver(signupSchema),
79
+ mode: 'onBlur', // Validate on blur for better UX
80
+ });
81
+
82
+ const onSubmit = async (data: SignupFormData) => {
83
+ const res = await fetch('/api/auth/signup', {
84
+ method: 'POST',
85
+ headers: { 'Content-Type': 'application/json' },
86
+ body: JSON.stringify(data),
87
+ });
88
+ if (!res.ok) throw new Error('Signup failed');
89
+ };
90
+
91
+ return (
92
+ <form onSubmit={handleSubmit(onSubmit)} noValidate>
93
+ <div>
94
+ <label htmlFor="name">Name</label>
95
+ <input
96
+ id="name"
97
+ {...register('name')}
98
+ aria-invalid={!!errors.name}
99
+ aria-describedby={errors.name ? 'name-error' : undefined}
100
+ autoComplete="name"
101
+ />
102
+ {errors.name && <p id="name-error" role="alert">{errors.name.message}</p>}
103
+ </div>
104
+
105
+ <div>
106
+ <label htmlFor="email">Email</label>
107
+ <input
108
+ id="email"
109
+ type="email"
110
+ {...register('email')}
111
+ aria-invalid={!!errors.email}
112
+ aria-describedby={errors.email ? 'email-error' : undefined}
113
+ autoComplete="email"
114
+ />
115
+ {errors.email && <p id="email-error" role="alert">{errors.email.message}</p>}
116
+ </div>
117
+
118
+ <div>
119
+ <label htmlFor="password">Password</label>
120
+ <input
121
+ id="password"
122
+ type="password"
123
+ {...register('password')}
124
+ aria-invalid={!!errors.password}
125
+ aria-describedby={errors.password ? 'password-error' : undefined}
126
+ autoComplete="new-password"
127
+ />
128
+ {errors.password && <p id="password-error" role="alert">{errors.password.message}</p>}
129
+ </div>
130
+
131
+ <div>
132
+ <label htmlFor="confirmPassword">Confirm Password</label>
133
+ <input
134
+ id="confirmPassword"
135
+ type="password"
136
+ {...register('confirmPassword')}
137
+ aria-invalid={!!errors.confirmPassword}
138
+ aria-describedby={errors.confirmPassword ? 'confirm-error' : undefined}
139
+ autoComplete="new-password"
140
+ />
141
+ {errors.confirmPassword && <p id="confirm-error" role="alert">{errors.confirmPassword.message}</p>}
142
+ </div>
143
+
144
+ <button type="submit" disabled={isSubmitting}>
145
+ {isSubmitting ? 'Creating account...' : 'Sign Up'}
146
+ </button>
147
+ </form>
148
+ );
149
+ }
150
+ ```
151
+
152
+ ---
153
+
154
+ ### 2. Server Actions + useActionState (React 19 / Next.js 15)
155
+
156
+ ```typescript
157
+ // actions/signup.ts
158
+ 'use server';
159
+
160
+ import { z } from 'zod';
161
+
162
+ const signupSchema = z.object({
163
+ name: z.string().min(2),
164
+ email: z.string().email(),
165
+ password: z.string().min(8),
166
+ });
167
+
168
+ export type SignupState = {
169
+ errors?: Record<string, string[]>;
170
+ message?: string;
171
+ success?: boolean;
172
+ };
173
+
174
+ export async function signupAction(
175
+ prevState: SignupState,
176
+ formData: FormData,
177
+ ): Promise<SignupState> {
178
+ const parsed = signupSchema.safeParse({
179
+ name: formData.get('name'),
180
+ email: formData.get('email'),
181
+ password: formData.get('password'),
182
+ });
183
+
184
+ if (!parsed.success) {
185
+ return {
186
+ errors: parsed.error.flatten().fieldErrors as Record<string, string[]>,
187
+ message: 'Please fix the errors below.',
188
+ };
189
+ }
190
+
191
+ // Check if email already exists
192
+ const existing = await db.query.users.findFirst({
193
+ where: eq(users.email, parsed.data.email),
194
+ });
195
+ if (existing) {
196
+ return { errors: { email: ['This email is already registered'] } };
197
+ }
198
+
199
+ // Create user
200
+ await createUser(parsed.data);
201
+ return { success: true, message: 'Account created!' };
202
+ }
203
+ ```
204
+
205
+ ```tsx
206
+ // components/signup-form-server.tsx
207
+ 'use client';
208
+
209
+ import { useActionState } from 'react';
210
+ import { signupAction, type SignupState } from '@/actions/signup';
211
+
212
+ export function SignupFormServer() {
213
+ const [state, formAction, isPending] = useActionState<SignupState, FormData>(
214
+ signupAction,
215
+ {},
216
+ );
217
+
218
+ return (
219
+ <form action={formAction}>
220
+ {state.message && (
221
+ <div role="alert" className={state.success ? 'success' : 'error'}>
222
+ {state.message}
223
+ </div>
224
+ )}
225
+
226
+ <div>
227
+ <label htmlFor="name">Name</label>
228
+ <input id="name" name="name" required autoComplete="name" />
229
+ {state.errors?.name && <p role="alert">{state.errors.name[0]}</p>}
230
+ </div>
231
+
232
+ <div>
233
+ <label htmlFor="email">Email</label>
234
+ <input id="email" name="email" type="email" required autoComplete="email" />
235
+ {state.errors?.email && <p role="alert">{state.errors.email[0]}</p>}
236
+ </div>
237
+
238
+ <div>
239
+ <label htmlFor="password">Password</label>
240
+ <input id="password" name="password" type="password" required minLength={8} autoComplete="new-password" />
241
+ {state.errors?.password && <p role="alert">{state.errors.password[0]}</p>}
242
+ </div>
243
+
244
+ <button type="submit" disabled={isPending}>
245
+ {isPending ? 'Creating...' : 'Sign Up'}
246
+ </button>
247
+ </form>
248
+ );
249
+ }
250
+ ```
251
+
252
+ ---
253
+
254
+ ### 3. Multi-Step Form Wizard
255
+
256
+ ```tsx
257
+ // components/multi-step-form.tsx
258
+ 'use client';
259
+
260
+ import { useState, useCallback } from 'react';
261
+ import { useForm, FormProvider, type UseFormReturn } from 'react-hook-form';
262
+ import { zodResolver } from '@hookform/resolvers/zod';
263
+ import { z } from 'zod';
264
+
265
+ // Step schemas
266
+ const step1Schema = z.object({
267
+ companyName: z.string().min(1, 'Required'),
268
+ industry: z.string().min(1, 'Required'),
269
+ });
270
+
271
+ const step2Schema = z.object({
272
+ contactName: z.string().min(1, 'Required'),
273
+ contactEmail: z.string().email(),
274
+ });
275
+
276
+ const step3Schema = z.object({
277
+ plan: z.enum(['starter', 'pro', 'enterprise']),
278
+ billingCycle: z.enum(['monthly', 'annual']),
279
+ });
280
+
281
+ const fullSchema = step1Schema.merge(step2Schema).merge(step3Schema);
282
+ type FormData = z.infer<typeof fullSchema>;
283
+
284
+ const STEP_SCHEMAS = [step1Schema, step2Schema, step3Schema] as const;
285
+ const STEP_TITLES = ['Company Info', 'Contact Details', 'Choose Plan'] as const;
286
+
287
+ export function MultiStepForm() {
288
+ const [currentStep, setCurrentStep] = useState(0);
289
+ const methods = useForm<FormData>({
290
+ resolver: zodResolver(fullSchema),
291
+ mode: 'onChange',
292
+ });
293
+
294
+ const goNext = useCallback(async () => {
295
+ const schema = STEP_SCHEMAS[currentStep];
296
+ const fields = Object.keys(schema.shape) as (keyof FormData)[];
297
+ const isValid = await methods.trigger(fields);
298
+ if (isValid) setCurrentStep(s => Math.min(s + 1, STEP_SCHEMAS.length - 1));
299
+ }, [currentStep, methods]);
300
+
301
+ const goBack = useCallback(() => {
302
+ setCurrentStep(s => Math.max(s - 1, 0));
303
+ }, []);
304
+
305
+ const onSubmit = async (data: FormData) => {
306
+ await fetch('/api/onboarding', {
307
+ method: 'POST',
308
+ body: JSON.stringify(data),
309
+ });
310
+ };
311
+
312
+ return (
313
+ <FormProvider {...methods}>
314
+ {/* Progress indicator */}
315
+ <nav aria-label="Form progress">
316
+ <ol className="step-progress">
317
+ {STEP_TITLES.map((title, i) => (
318
+ <li key={i} className={i === currentStep ? 'active' : i < currentStep ? 'completed' : ''}>
319
+ {title}
320
+ </li>
321
+ ))}
322
+ </ol>
323
+ </nav>
324
+
325
+ <form onSubmit={methods.handleSubmit(onSubmit)}>
326
+ {currentStep === 0 && <Step1 />}
327
+ {currentStep === 1 && <Step2 />}
328
+ {currentStep === 2 && <Step3 />}
329
+
330
+ <div className="button-group">
331
+ {currentStep > 0 && <button type="button" onClick={goBack}>Back</button>}
332
+ {currentStep < STEP_SCHEMAS.length - 1
333
+ ? <button type="button" onClick={goNext}>Next</button>
334
+ : <button type="submit">Submit</button>
335
+ }
336
+ </div>
337
+ </form>
338
+ </FormProvider>
339
+ );
340
+ }
341
+ ```
342
+
343
+ ---
344
+
345
+ ### 4. Form Accessibility Checklist
346
+
347
+ | Requirement | Implementation |
348
+ |---|---|
349
+ | **Labels** | Every input has a visible `<label>` with `htmlFor` matching input `id` |
350
+ | **Error identification** | `aria-invalid="true"` on invalid fields |
351
+ | **Error description** | `aria-describedby` pointing to error message element |
352
+ | **Error announcement** | Error messages use `role="alert"` for screen readers |
353
+ | **Required fields** | Use `aria-required="true"` or native `required` attribute |
354
+ | **Autofill** | Correct `autoComplete` values (`name`, `email`, `new-password`) |
355
+ | **Focus management** | Focus moves to first error on submit failure |
356
+ | **Submit state** | Button shows loading state and is disabled during submission |
357
+
358
+ ---
359
+
360
+ ### Common Pitfalls to Avoid
361
+
362
+ | Anti-Pattern | Problem | Correct Approach |
363
+ |---|---|---|
364
+ | Validating only on submit | Late error discovery, poor UX | Validate `onBlur` or `onChange` |
365
+ | Client-side validation only | Can be bypassed | Always validate server-side too |
366
+ | No `autoComplete` attributes | Misses browser autofill | Add correct `autoComplete` values |
367
+ | Generic error messages | "Invalid field" doesn't help | Specific: "Must be at least 8 characters" |
368
+ | Resetting form on error | User loses all input | Preserve input, highlight errors |
369
+ | No loading state on submit | User clicks multiple times | Disable button + show spinner |
370
+
371
+ ---
372
+
373
+ ### Integration with Other Skills
374
+
375
+ - `senior-frontend` — React 19 form patterns, useActionState, useOptimistic
376
+ - `global-a11y-i18n-expert` — WCAG 2.2 form accessibility requirements
377
+ - `ui-components-expert` — Input control primitives, form field components
378
+ - `authentication-identity-expert` — Login/signup form patterns
379
+ - `error-resilience-expert` — Form submission error handling and retry
380
+ - `tailwind-expert` — Form styling with Tailwind CSS v4
381
+
382
+ ---
383
+
384
+ <a name="bahasa-indonesia"></a>
385
+ ## Bahasa Indonesia
386
+
387
+ ### Integrasi Orkestrasi
388
+ Terhubung dan mengorkestrasi skill domain yang relevan seperti `brainstorming`, `zero-to-prod-orchestrator`, dan `project-context-mapper` untuk memastikan eksekusi yang kohesif.
389
+
390
+ ### Deskripsi
391
+ Panduan tingkat produksi untuk membangun formulir yang kuat, aksesibel, dan ramah pengguna. Mencakup **React Hook Form v7+** dengan Zod/Valibot resolver, **validasi server-side** dengan `useActionState` + Zod, **wizard formulir multi-langkah**, **formulir dinamis** dari JSON Schema, **pola autofill-friendly**, **optimistic form submission**, **Conform** (progressive enhancement), dan **aksesibilitas formulir** (ARIA, pengumuman error).
392
+
393
+ ### Kondisi Pemicu
394
+ Aktifkan skill ini ketika:
395
+ - Membangun formulir dengan validasi client-side (React Hook Form, Zod, Valibot).
396
+ - Mengimplementasikan validasi formulir server-side dengan React 19 Server Actions.
397
+ - Membuat wizard formulir multi-langkah dengan persistensi state.
398
+ - Membangun formulir dinamis dari definisi skema.
399
+ - Membuat formulir yang aksesibel (atribut ARIA, pengumuman error).
400
+
401
+ ### Integrasi dengan Skill Lain
402
+
403
+ - `senior-frontend` — Pola formulir React 19, useActionState, useOptimistic
404
+ - `global-a11y-i18n-expert` — Persyaratan aksesibilitas formulir WCAG 2.2
405
+ - `ui-components-expert` — Primitif kontrol input, komponen field formulir
406
+ - `authentication-identity-expert` — Pola formulir login/signup
407
+ - `error-resilience-expert` — Penanganan error submission formulir dan retry