form-refactorer
Refactor legacy forms to use modern Redpanda UI Registry Field components with react-hook-form and Zod validation. Use when user requests: (1) Form refactoring or modernization, (2) Converting Chakra UI or @redpanda-data/ui forms, (3) Updating forms to use Field components, (4) Migrating from legacy form patterns, (5) Implementing forms with react-hook-form and Zod validation.
When & Why to Use This Skill
This Claude skill automates the modernization of legacy React forms by refactoring them into the latest Redpanda UI Registry Field components. It streamlines the transition from outdated libraries like Chakra UI or Yup to a high-performance, type-safe stack featuring react-hook-form and Zod validation. By enforcing modern coding standards and accessibility best practices, it helps developers significantly reduce technical debt and improve form maintainability.
Use Cases
- Modernizing legacy Chakra UI or @redpanda-data/ui form structures to the standardized Redpanda UI Registry system.
- Migrating form validation logic from Yup to Zod to leverage better TypeScript integration and schema-based validation.
- Refactoring manual or legacy form state management to react-hook-form to minimize re-renders and boilerplate code.
- Ensuring web accessibility (A11y) compliance by automatically implementing correct ARIA attributes, label associations, and semantic FieldSet structures.
- Standardizing complex form layouts across a codebase using consistent FieldGroup and Field component hierarchies.
| name | form-refactorer |
|---|---|
| description | Refactor legacy forms to use modern Redpanda UI Registry Field components with react-hook-form and Zod validation. Use when user requests: (1) Form refactoring or modernization, (2) Converting Chakra UI or @redpanda-data/ui forms, (3) Updating forms to use Field components, (4) Migrating from legacy form patterns, (5) Implementing forms with react-hook-form and Zod validation. |
Form Refactorer
Refactor legacy forms to modern Redpanda UI Registry Field components with react-hook-form and Zod validation.
Core Requirements
MUST USE (Modern Pattern)
import {
FieldSet,
FieldLegend,
FieldGroup,
Field,
FieldLabel,
FieldContent,
FieldDescription,
FieldError,
FieldSeparator
} from "components/redpanda-ui/components/field";
- react-hook-form for form state management
- Zod for validation schemas via
zodResolver - Field component family for all form layouts
FORBIDDEN (Legacy Patterns)
See no-legacy for prohibited patterns.
// ❌ Also never import legacy form components
import { Form } from "components/redpanda-ui/components/form";
Refactoring Workflow
Step 1: Identify Legacy Patterns
Scan the form file for these indicators:
Legacy imports to remove:
@chakra-ui/reactform components (FormControl, FormLabel, FormErrorMessage, FormHelperText)@redpanda-data/uiForm componentcomponents/redpanda-ui/components/form(legacy)- Yup validation imports
Legacy patterns to replace:
<FormControl isInvalid={...}>→<Field data-invalid={...}><FormLabel>→<FieldLabel htmlFor="..."><FormErrorMessage>→<FieldError><FormHelperText>→<FieldDescription>- Yup schemas → Zod schemas
Step 2: Set Up Modern Dependencies
Add required imports:
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import {
FieldSet,
FieldLegend,
FieldGroup,
Field,
FieldLabel,
FieldError,
FieldDescription
} from "components/redpanda-ui/components/field";
Create Zod schema replacing Yup:
// Replace Yup schema
const schema = z.object({
name: z.string().min(1, "Name is required"),
email: z.string().email("Invalid email address"),
age: z.number().min(18, "Must be 18 or older"),
});
// Use with react-hook-form
const { register, handleSubmit, formState: { errors } } = useForm({
resolver: zodResolver(schema),
});
Step 3: Restructure Form Layout
Replace legacy structure with Field components:
Pattern: Simple vertical field
<Field data-invalid={!!errors.fieldName}>
<FieldLabel htmlFor="fieldName">Field Label</FieldLabel>
<Input
id="fieldName"
{...register("fieldName")}
aria-invalid={!!errors.fieldName}
/>
<FieldDescription>Optional helper text</FieldDescription>
{errors.fieldName && <FieldError>{errors.fieldName.message}</FieldError>}
</Field>
Pattern: Horizontal field (switch/checkbox)
<Field orientation="horizontal">
<Switch id="toggle" {...register("toggle")} />
<FieldContent>
<FieldLabel htmlFor="toggle">Enable Feature</FieldLabel>
<FieldDescription>This enables the feature</FieldDescription>
</FieldContent>
</Field>
Pattern: Multiple fields in group
<FieldSet>
<FieldLegend>Section Title</FieldLegend>
<FieldDescription>Section description (optional)</FieldDescription>
<FieldGroup>
<Field>...</Field>
<Field>...</Field>
<FieldSeparator /> {/* Optional visual divider */}
<Field>...</Field>
</FieldGroup>
</FieldSet>
Step 4: Handle Validation Errors
Apply error states to both Field and Input:
<Field data-invalid={!!errors.email}>
<FieldLabel htmlFor="email">Email</FieldLabel>
<Input
id="email"
{...register("email")}
aria-invalid={!!errors.email} // For assistive technologies
/>
{errors.email && <FieldError>{errors.email.message}</FieldError>}
</Field>
Step 5: Ensure Accessibility
Required accessibility attributes:
- Label association: Use
htmlForon FieldLabel matching Inputid - Error state: Add
data-invalidto Field when error exists - ARIA invalid: Add
aria-invalidto Input when error exists - Semantic grouping: Use FieldSet + FieldLegend for related fields
// ✅ Correct accessibility
<Field data-invalid={!!errors.name}>
<FieldLabel htmlFor="name">Name</FieldLabel>
<Input id="name" {...register("name")} aria-invalid={!!errors.name} />
{errors.name && <FieldError>{errors.name.message}</FieldError>}
</Field>
Quick Reference
Component Hierarchy
FieldSet - Semantic fieldset container for related fields
├─ FieldLegend - Title for the fieldset
├─ FieldDescription - Description for entire fieldset
└─ FieldGroup - Layout wrapper that stacks fields
├─ Field - Individual field wrapper
│ ├─ FieldLabel - Label for input (use htmlFor)
│ ├─ Input/Select/... - Form control
│ ├─ FieldDescription - Helper text
│ └─ FieldError - Error message
├─ FieldSeparator - Visual divider between fields
└─ Field - Another field
Common Zod Patterns
// String validations
z.string().min(1, "Required")
z.string().email("Invalid email")
z.string().url("Invalid URL")
z.string().regex(/^[A-Z]/, "Must start with capital")
// Number validations
z.number().min(0, "Must be positive")
z.number().max(100, "Max 100")
z.number().int("Must be integer")
// Optional fields
z.string().optional()
z.string().nullable()
// Arrays
z.array(z.string()).min(1, "At least one required")
// Objects
z.object({
nested: z.string(),
})
// Conditional validation
z.object({
type: z.enum(["a", "b"]),
value: z.string(),
}).refine((data) => {
if (data.type === "a") return data.value.length > 5;
return true;
}, "Value must be longer than 5 for type A")
Advanced Scenarios
For complex patterns (dynamic fields, field arrays, multi-section forms), see:
- references/common-patterns.md - Detailed patterns for advanced use cases
- references/migration-examples.md - Before/after examples
Checklist
Before completing refactoring, verify:
- All legacy form imports removed (Chakra, @redpanda-data/ui, legacy Form)
- Field components used for all fields
- FieldLabel has
htmlFormatching Inputid - Errors show
data-invalidon Field andaria-invalidon Input - Yup replaced with Zod + zodResolver
- FieldSet + FieldLegend used for semantic grouping
- FieldError conditionally rendered:
{errors.field && <FieldError>... - No inline styling or className for layout (use Field orientation/FieldGroup)
- Form submission handled via react-hook-form
handleSubmit
Documentation
Full Field component documentation: https://redpanda-ui-registry.netlify.app/docs/field