Submitters who discover missing or invalid fields only after hitting submit waste a full network round-trip, lose their place in the form, and frequently abandon. Directories that rely on server-only validation bleed 20-40% of completion rate on multi-field listings and push that friction onto the moderation queue as half-filled drafts. Inline field-level errors on blur catch mistakes at the point of input, keep scroll position intact, and give assistive technologies an accessible hook to announce problems, which matters directly to WCAG 2.2 SC 3.3.1 (Error Identification) and SC 3.3.3 (Error Suggestion) conformance.
Critical because missing client validation directly tanks submission completion and violates WCAG 3.3.1/3.3.3 accessibility requirements.
Wire a schema-driven form in components/SubmissionForm.tsx so every required field validates on blur and renders its error inline next to the input, not in a toast. Use react-hook-form with zodResolver so the same schema can be shared with server handlers:
const schema = z.object({
title: z.string().min(10, 'Title must be at least 10 characters'),
description: z.string().min(50, 'Description must be at least 50 characters'),
contactEmail: z.string().email('Invalid email address')
})
Render {errors.title && <span role="alert">{errors.title.message}</span>} under each input so screen readers announce the failure.
ID: directory-submissions-moderation.submission-form.client-validation
Severity: critical
What to look for: Examine the submission form component. Look for client-side validation that checks required fields (title, description, contact email, etc.) and displays error messages inline or below each field. The validation should run as the user types or on blur, not just on form submit.
Pass criteria: Enumerate all relevant code paths. All required fields are validated client-side. Error messages appear inline or immediately below the field (not in a toast or modal). Validation runs on blur or input change, providing real-time feedback. On pass, report the count of validated fields vs. total fields.
Fail criteria: No client-side validation, or validation only runs on form submit with no inline errors, or validation exists but is incomplete (doesn't check all required fields). A partial implementation does not count as pass.
Skip (N/A) when: The project has no submission form or uses a third-party service that handles form validation.
Detail on fail: "Form has no client-side validation. User submits with empty title field and receives only a generic server error." or "Validation exists but errors display only in a toast notification, not inline."
Cross-reference: Compare with directory-submissions-moderation.submission-form.server-validation — client validation improves UX; server validation enforces data integrity.
Remediation: Implement client-side validation with inline error display:
// components/SubmissionForm.tsx
import { useForm } from 'react-hook-form'
import { z } from 'zod'
import { zodResolver } from '@hookform/resolvers/zod'
const schema = z.object({
title: z.string().min(1, 'Title is required').min(10, 'Title must be at least 10 characters'),
description: z.string().min(1, 'Description is required').min(50, 'Description must be at least 50 characters'),
contactEmail: z.string().email('Invalid email address'),
phone: z.string().optional()
})
export function SubmissionForm() {
const { register, formState: { errors }, handleSubmit } = useForm({
resolver: zodResolver(schema)
})
return (
<form onSubmit={handleSubmit(onSubmit)}>
<div>
<label htmlFor="title">Listing Title *</label>
<input id="title" {...register('title')} />
{errors.title && <span className="error">{errors.title.message}</span>}
</div>
<div>
<label htmlFor="description">Description *</label>
<textarea id="description" {...register('description')} />
{errors.description && <span className="error">{errors.description.message}</span>}
</div>
<div>
<label htmlFor="contactEmail">Contact Email *</label>
<input id="contactEmail" type="email" {...register('contactEmail')} />
{errors.contactEmail && <span className="error">{errors.contactEmail.message}</span>}
</div>
<button type="submit">Submit Listing</button>
</form>
)
}