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)
// ❌ Never import or use these
import { Form } from "components/redpanda-ui/components/form";
import { Form, FormControl, FormLabel } from "@redpanda-data/ui";
import { FormControl, FormLabel } from "@chakra-ui/react";
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