Skills DirectorySkills Directory
SkillsLearnSecurityCategoriesDocsCommunityBlog
Sign InSubmit Skill
Skills Directory

Security-tested agent skills for Claude, coding agents, and AI workflows.

Directory

  • Browse Skills
  • All Skills A–Z
  • Claude Skills
  • Claude Code Skills
  • Agent Skills
  • Categories
  • Submit a Skill

Learn

  • Learn Hub
  • Install Claude Skills
  • Write SKILL.md
  • Skills vs MCP
  • Directories Compared

Security

  • Security
  • Methodology
  • Secure Claude Skills
  • Security Badges

Company

  • About
  • Community
  • Blog
  • API Docs
  • Advertise

2026 Skills Directory. All rights reserved.

Back to skills

Frontend Form Pattern

ASecurity

Frontend form patterns — form types in model/, useForm setup, and end-to-end backend validation with field-level error display. Use when adding or modifying any form (page or dialog) that submits to the API.

33 stars
0 votes
0 copies
0 views
Added 9/20/2026
developmenttypescriptreactapifrontendbackend

Works with

cliapi

Security Analysis

A100/100

Scanned 9/20/2026

Install to Claude Code

$npx -y skills add ayunis-core/ayunis-core --skill frontend-form-pattern --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Frontend Form Pattern?

Add the live security badge to your README — it updates automatically with every re-scan.

Security grade badge for Frontend Form Pattern
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/ayunis-core-frontend-form-pattern/badge)](https://www.skillsdirectory.com/skills/ayunis-core-frontend-form-pattern)

More formats (shields.io, HTML) on the badges page.

Download Zip
Files
SKILL.md
---
name: frontend-form-pattern
description: Frontend form patterns — form types in model/, useForm setup, and end-to-end backend validation with field-level error display. Use when adding or modifying any form (page or dialog) that submits to the API.
---

# Frontend Form Pattern

This skill covers how forms are structured in page modules and the complete validation flow from backend DTO → API response → frontend form field errors with i18n.

## Form Types

Form field interfaces live in the page module's `model/` directory — never inline in the component file.

```typescript
// model/types.ts
export interface ThingFormFields {
  name: string;
  description: string;
}
```

Multiple form types can share the same file (e.g. `CreateThingFormFields` and `UpdateThingFormFields`), or use separate files if they diverge significantly.

The component imports the type:

```typescript
// ui/ThingDetailPage.tsx
import type { ThingFormFields } from '../model/types';

const form = useForm<ThingFormFields>({
  defaultValues: { name: entity.name, description: entity.description ?? '' },
});
```

### Existing examples

- `pages/admin-settings/teams-settings/model/types.ts` — `CreateTeamFormData`, `UpdateTeamFormData`
- `pages/admin-settings/letterhead-detail/model/types.ts` — `LetterheadFormFields`
- `pages/prompts/model/createPromptSchema.ts` — Zod schema with inferred type

## Page Component Structure

Page components (`ui/`) should contain only component logic: state, hooks, form setup, event handlers, and JSX. Keep them focused:

- **Form types** → `model/types.ts`
- **Form setup** (`useForm`, `defaultValues`, `onSubmit`) stays in the component — it's component logic
- **Pure helper functions** → `lib/` (see the `ayunis-core-frontend-dev` skill)

## Validation

### Overview

```text
Backend DTO (@MaxLength, @IsNotEmpty, …)
  ↓ ValidationPipe rejects
  ↓ exceptionFactory in main.ts formats response
  ↓
{ code: "VALIDATION_ERROR", errors: [{ field, constraints }] }
  ↓
Frontend hook: extractErrorData() → setValidationErrors(form, …)
  ↓
form.setError(field, { message: t("validation.field.constraint") })
  ↓
<FormMessage /> renders inline error
```

### Backend

#### DTO (class-validator decorators)

Validation rules live on the DTO. These are the source of truth — they also feed the OpenAPI spec and generated frontend client.

```typescript
// presenters/http/dto/create-thing.dto.ts
import { IsString, IsNotEmpty, MaxLength } from 'class-validator';

export class CreateThingDto {
  @IsString()
  @IsNotEmpty()
  @MaxLength(100)
  name: string;

  @IsString()
  @IsNotEmpty()
  @MaxLength(500)
  description: string;
}
```

#### Global ValidationPipe (already configured in `main.ts`)

The `exceptionFactory` normalizes validation errors into a structured response with a `VALIDATION_ERROR` code and field-level detail. **You don't need to touch this** — it applies globally to all DTOs.

Response format:

```json
{
  "code": "VALIDATION_ERROR",
  "message": "Validation failed",
  "errors": [
    { "field": "description", "constraints": ["maxLength"] }
  ]
}
```

Nested DTOs produce dot-notation field names (e.g. `"address.street"`).

#### Domain errors (ApplicationError)

Domain-level errors (e.g. duplicate name, not found) are separate from DTO validation. They use the `ApplicationError` pattern with module-specific error codes:

```typescript
// application/thing.errors.ts
export class DuplicateThingNameError extends ThingError {
  constructor(name: string) {
    super(`Thing "${name}" already exists`, ThingErrorCode.DUPLICATE_NAME, 409);
  }
}
```

These flow through `ApplicationErrorFilter` and arrive at the frontend with their own `code`.

### Frontend

#### `extractErrorData` (shared, already exists)

Extracts `code`, `message`, `status`, and `errors` from Axios errors:

```typescript
import extractErrorData from '@/shared/api/extract-error-data';
// Returns: { code, message, status, errors?: FieldError[] }
```

#### `setValidationErrors` (shared helper)

Maps backend field errors to `form.setError()` with translated messages.

```typescript
import { setValidationErrors } from '@/shared/lib/set-validation-errors';
```

Translation key resolution order:

1. `${prefix}.${field}.${constraint}` — e.g. `validation.description.maxLength`
2. `${prefix}.${field}.invalid` — e.g. `validation.description.invalid`
3. `${prefix}.invalid` — generic fallback

#### Hook pattern

The hook receives the `form` instance so it can set field errors on validation failure. Other error codes (domain errors) show toast messages as before.

```typescript
// api/useCreateThing.ts
import type { UseFormReturn } from 'react-hook-form';
import { showSuccess, showError } from '@/shared/lib/toast';
import extractErrorData from '@/shared/api/extract-error-data';
import { setValidationErrors } from '@/shared/lib/set-validation-errors';

export function useCreateThing(
  form: UseFormReturn<ThingFormData>,
  onSuccess?: () => void,
) {
  const { t } = useTranslation('things');
  const mutation = useThingsControllerCreate({
    mutation: {
      onSuccess: async () => {
        showSuccess(t('toast.createSuccess'));
        onSuccess?.();
      },
      onError: (error: unknown) => {
        try {
          const { code, errors } = extractErrorData(error);
          if (code === 'VALIDATION_ERROR' && errors) {
            setValidationErrors(form, errors, t, 'validation');
          } else if (code === 'DUPLICATE_NAME') {
            showError(t('toast.duplicateName'));
          } else {
            showError(t('toast.createError'));
          }
        } catch {
          showError(t('toast.createError'));
        }
      },
    },
  });

  return {
    createThing: (data: CreateThingDto) => mutation.mutate({ data }),
    isCreating: mutation.isPending,
  };
}
```

If a hook is used **without a form** (e.g. a toggle), make `form` optional and guard the `setValidationErrors` call:

```typescript
export function useUpdateThing(
  form?: UseFormReturn<ThingFormData>,
  onSuccess?: () => void,
) {
  // ...
  if (code === 'VALIDATION_ERROR' && errors && form) {
    setValidationErrors(form, errors, t, 'validation');
  }
}
```

#### Dialog component

Pass the `form` to the hook:

```typescript
const form = useForm<ThingFormData>({ defaultValues: { name: '', description: '' } });
const { createThing, isCreating } = useCreateThing(form, () => {
  onOpenChange(false);
  form.reset();
});
```

The form fields must use `<FormMessage />` (from shadcn) to render errors inline — this is already standard in all form dialogs.

#### Translation keys

Add a `validation` section to the page's translation namespace:

```json
{
  "validation": {
    "name": {
      "isNotEmpty": "Name is required",
      "maxLength": "Name must be 100 characters or fewer",
      "invalid": "Invalid name"
    },
    "description": {
      "isNotEmpty": "Description is required",
      "maxLength": "Description must be 500 characters or fewer",
      "invalid": "Invalid description"
    },
    "invalid": "Invalid input"
  }
}
```

Constraint keys match the class-validator decorator names in camelCase: `isNotEmpty`, `maxLength`, `isString`, `isEnum`, `isEmail`, `minLength`, `length`, `matches`, etc.

## Checklist

- [ ] Form types defined in `model/types.ts`, not inline in the component
- [ ] DTO has class-validator decorators for all constraints
- [ ] Hook accepts `form` parameter and calls `setValidationErrors` on `VALIDATION_ERROR`
- [ ] Hook still handles domain error codes (e.g. duplicate) via toast
- [ ] Dialog passes `form` to the hook
- [ ] Translation file has `validation.{field}.{constraint}` keys in all locales (en, de)
- [ ] Form fields use `<FormMessage />` for inline error display

## Reference implementation

See the skill templates feature for a complete example:

- Backend DTO: `ayunis-core-backend/src/domain/skill-templates/presenters/http/dto/create-skill-template.dto.ts`
- Frontend hook: `ayunis-core-frontend/src/pages/super-admin-settings/skill-templates/api/useCreateSkillTemplate.ts`
- Frontend dialog: `ayunis-core-frontend/src/pages/super-admin-settings/skill-templates/ui/CreateSkillTemplateDialog.tsx`
- Translations: `ayunis-core-frontend/src/shared/locales/en/super-admin-settings-skills.json`
- Shared helper: `ayunis-core-frontend/src/shared/lib/set-validation-errors.ts`
- Error extractor: `ayunis-core-frontend/src/shared/api/extract-error-data.ts`
- ValidationPipe config: `ayunis-core-backend/src/main.ts` (`exceptionFactory`)

Attribution

ayunis-coreayunis-core
View sourceMore from ayunis-core →
SSkills DirectorySkills Directory

Your tool, in front of Claude Code builders.

3 founder slots · $299/mo · GSC-verified traffic · sponsors can never buy grades.

See placements

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments (0)

No comments yet. Be the first to comment!

SSkills DirectorySkills Directory

Your tool, in front of Claude Code builders.

3 founder slots · $299/mo · GSC-verified traffic · sponsors can never buy grades.

See placements

Related Skills

Browser Extension Developer

Use this skill when developing or maintaining browser extension code in the `browser/` directory, including Chrome/Firefox/Edge compatibility, content scripts, background scripts, or i18n updates.

281612 votes

Seo Optimizer

SEO optimization with keyword analysis, readability assessment, technical validation, content quality. Use for search rankings, blog posts, content audits, or encountering keyword density, readability scores, meta tags, schema markup errors.

2132 votes

Google Official Seo Guide

Official Google SEO guide covering search optimization, best practices, Search Console, crawling, indexing, and improving website search visibility based on official Google documentation

1862 votes

Tanstack Start

Build a full-stack TanStack Start app on Cloudflare Workers from scratch — SSR, file-based routing, server functions, D1+Drizzle, better-auth, Tailwind v4+shadcn/ui. Use whenever the user mentions TanStack Start, asks to scaffold a full-stack Cloudflare app with SSR, wants an SSR dashboard, or asks for a React 19 + Cloudflare Workers app with file-based routing and server functions — even if they don't name TanStack Start specifically. No template repo — Claude generates every file fresh per ...

9881 votes

Pentest

PTES-aligned adversarial security audit for backend, frontend, and mobile applications. Produces a CVSS-scored Hacker Report with verified PoCs and phased remediation.

5491 votes
View all in development →