الگوهای فرم
راهنمای طراحی فرمهای قابل استفاده، دسترسپذیر، و سازگار با RTL در پرتو
اصول طراحی فرم
فرمها رایجترین نقطه تعامل کاربر با محصول هستند. یک فرم خوب:
- واضح است — کاربر میداند چه باید وارد کند
- بازخورد میدهد — خطاها و موفقیتها فوراً نمایش داده میشوند
- مقاوم در برابر خطا است — وقتی اشتباهی رخ میدهد، کاربر میداند چه کاری بکند
- قابل دسترسی است — با صفحهکلید و screen reader کار میکند
نمونه بصری
ساختار پایه
import { Form, FormField, FormItem, FormLabel, FormControl, FormMessage } from '@parto-system-design/ui'
import { Input, Button } from '@parto-system-design/ui'
import { useForm } from 'react-hook-form'
export function ExampleForm() {
const form = useForm()
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
<FormField
control={form.control}
name="username"
render={({ field }) => (
<FormItem>
<FormLabel>نام کاربری</FormLabel>
<FormControl>
<Input placeholder="نام کاربری خود را وارد کنید" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Button type="submit">ثبت</Button>
</form>
</Form>
)
}نمایش خطا
خطای inline (پایین فیلد)
<FormField
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>ایمیل</FormLabel>
<FormControl>
<Input type="email" {...field} />
</FormControl>
<FormMessage /> {/* خطا اینجا نمایش داده میشود */}
</FormItem>
)}
/>خطای کلی فرم (بالای فرم)
{form.formState.errors.root && (
<Alert variant="destructive">
<AlertTitle>خطا در ارسال</AlertTitle>
<AlertDescription>
{form.formState.errors.root.message}
</AlertDescription>
</Alert>
)}فرمهای چند مرحلهای
const [step, setStep] = useState(1)
return (
<div className="space-y-6">
{/* نوار پیشرفت */}
<div className="flex gap-2 items-center">
{[1, 2, 3].map((s) => (
<div
key={s}
className={cn(
'h-2 flex-1 rounded-full',
s <= step ? 'bg-brand' : 'bg-surface-300'
)}
/>
))}
</div>
{/* مرحله ۱ */}
{step === 1 && (
<div className="space-y-4">
<h2 className="text-lg font-semibold">اطلاعات پایه</h2>
<FormField name="name" render={...} />
<Button onClick={() => setStep(2)}>مرحله بعد</Button>
</div>
)}
{/* مرحله ۲ */}
{step === 2 && (
<div className="space-y-4">
<h2 className="text-lg font-semibold">اطلاعات تماس</h2>
<FormField name="email" render={...} />
<div className="flex gap-2">
<Button variant="outline" onClick={() => setStep(1)}>قبلی</Button>
<Button onClick={() => setStep(3)}>مرحله بعد</Button>
</div>
</div>
)}
</div>
)Validation
با Zod
import { z } from 'zod'
import { zodResolver } from '@hookform/resolvers/zod'
const schema = z.object({
username: z
.string()
.min(3, 'نام کاربری باید حداقل ۳ کاراکتر باشد')
.max(20, 'نام کاربری نباید بیش از ۲۰ کاراکتر باشد'),
email: z
.string()
.email('آدرس ایمیل نامعتبر است'),
password: z
.string()
.min(8, 'رمز عبور باید حداقل ۸ کاراکتر باشد'),
})
const form = useForm({
resolver: zodResolver(schema),
defaultValues: { username: '', email: '', password: '' },
})دسترسیپذیری فرمها
// همیشه label با input مرتبط باشد
<FormLabel htmlFor="email">ایمیل</FormLabel>
<Input id="email" aria-describedby="email-error" />
<FormMessage id="email-error" />
// فیلدهای اجباری مشخص شوند
<FormLabel>
ایمیل
<span aria-hidden="true" className="text-destructive ms-1">*</span>
<span className="sr-only"> (اجباری)</span>
</FormLabel>
// خطاها با role="alert" اعلام شوند
<div role="alert" aria-live="polite">
<FormMessage />
</div>بهترین روشها
- label را بالای فیلد قرار دهید، نه کنار آن — برای RTL و LTR هر دو بهتر است
- خطا را پایین فیلد مربوطه نمایش دهید، نه در یک خلاصه کلی
- دکمه submit را پس از آخرین فیلد قرار دهید
- از placeholder برای مثال استفاده کنید، نه برای توضیح
- فیلدهای اجباری را با
*مشخص کنید - بعد از submit موفق، یک feedback واضح نمایش دهید (toast یا success state)
صفحات مرتبط
- الگوهای خطا — نمایش خطاهای API و سطح صفحه
- محتوا و لحن — قوانین نوشتن label، placeholder، و پیام خطا
- دسترسیپذیری — ارتباط label با input، role="alert"