# Frontend Vite Forms

> Vite admin forms with React Hook Form, Zod, shadcn Form, createSubmitHandler, FormSubmitButton, bilingual fields. Use when building create/edit forms in admin dashboards.

- Skill: `xmuhameed/frontend-vite-forms` (Agent Skill)
- Install (CLI): `npx skillmds@latest add xmuhameed/frontend-vite-forms`
- Raw SKILL.md: https://api.skillmd.com/api/skills/xmuhameed/frontend-vite-forms/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Web & Frontend
- Author: xmuhameed (https://skillmd.com/u/xmuhameed)
- Updated: 2026-09-21
- Page: https://skillmd.com/skills/xmuhameed/frontend-vite-forms

---


# Admin Forms

## Standard Setup

```typescript
const schema = z.object({
  name_en: z.string().min(1, 'Required'),
  name_ar: z.string().min(1, 'Required'),
  sort: z.coerce.number().min(0),
});
type FormData = z.infer<typeof schema>;

const form = useForm<FormData>({
  resolver: zodResolver(schema),
  defaultValues: { name_en: '', name_ar: '', sort: 0 },
});
```

## shadcn Form

```tsx
<Form {...form}>
  <form onSubmit={createSubmitHandler(form, onValid)}>
    <FormField control={form.control} name="name_en" render={({ field }) => (
      <FormItem><FormLabel>Name (EN)</FormLabel><FormControl><Input {...field} /></FormControl><FormMessage /></FormItem>
    )} />
    <FormSubmitButton isLoading={mutation.isPending}>Save</FormSubmitButton>
  </form>
</Form>
```

## Create/Edit Unified

```typescript
const { id } = useParams();
const isEditing = Boolean(id);
const { data } = useGetCategory(id!, { enabled: isEditing });
useEffect(() => { if (data) form.reset(data); }, [data]);
```

## Form Utilities

```typescript
// lib/form-utils.ts
createSubmitHandler(form, onValid, { getExtraValidationErrors? })
showFormValidationToast(errors)  // nested RHF errors → Sonner
onFormInvalid                    // default handler
```

## Bilingual Fields

Always pair: `name_en`/`name_ar`, `metaTitle_en`/`metaTitle_ar`. UI labels English.

## File Inputs

**Not in RHF** — separate state:

```typescript
const [imageFile, setImageFile] = useState<File | null>(null);
const [preview, setPreview] = useState<string | null>(null);
// FileReader for preview; validate MIME + max 5MB
// Pass File in mutation payload → api.ts builds FormData
```

## Reusable Form Component

```typescript
// components/DeliverySlotForm.tsx
interface Props {
  initialData?: DeliverySlot;
  onSubmit: (data: FormData) => Promise<void>;
  isLoading: boolean;
}
// Parent page owns mutation + navigation
```

## Simple Dialog Forms

For lookup tables (colors, coupons): local `useState` formData OR RHF inside Dialog — both acceptable.

