پرتوپرتو

الگوهای فرم

راهنمای طراحی فرم‌های قابل استفاده، دسترس‌پذیر، و سازگار با 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)

صفحات مرتبط