حالت‌های فوکوس و اعتبارسنجی؛ وقتی فرم باید با کاربر حرف بزنه

تو دو درس قبل، با ظاهر پایه‌ی فرم‌ها و ریزه‌کاری‌های چک‌باکس و رادیو آشنا شدیم. اما یه فرم خوب فقط قشنگ به نظر رسیدن نیست؛ باید بتونه با کاربر «حرف بزنه»: کجا الان فوکوس داره، کدوم فیلد پر شده اشتباهه، کدوم فیلد اجباریه. تو این درس می‌خوایم دقیق‌تر بریم سراغ pseudo-class هایی که این ارتباط رو ممکن می‌کنن.

فرق focus، focus-visible و focus-within

تو درس اول این فصل با :focus-visible آشنا شدیم. حالا وقتشه سه‌تا رو کنار هم مقایسه کنیم:

Pseudo-classفعال می‌شه وقتی
:focusخود المنت فوکوس داره، صرف‌نظر از این‌که فوکوس از کیبورد اومده یا کلیک
:focus-visibleفوکوس داره و مرورگر تشخیص می‌ده باید نشونه‌ی بصری نشون بده (معمولاً یعنی از کیبورد اومده)
:focus-withinخود المنت یا هر فرزندی داخلش، فوکوس داره

:focus-within خصوصاً برای گروه‌بندی input ها خیلی کاربردیه؛ مثلاً وقتی می‌خواید کل یه fieldset یا wrapper، وقتی هر کدوم از input های داخلش فوکوس گرفت، یه استایل خاص بگیره:

.form-group {
  border: 1px solid #ddd;
  transition: border-color 0.2s;
}

.form-group:focus-within {
  border-color: #3b82f6;
}

این‌جوری، به‌جای این‌که فقط خود input رنگ عوض کنه، کل wrapper (شامل label و هر عنصر دیگه‌ای داخلش) واکنش نشون می‌ده.

Pseudo-class های اعتبارسنجی: :valid و :invalid

وقتی یه input attribute هایی مثل required، type="email" یا pattern داشته باشه، مرورگر خودش validation انجام می‌ده و بر اساس نتیجه، یکی از این دو pseudo-class فعال می‌شه:

input:invalid {
  border-color: #dc2626;
}

input:valid {
  border-color: #16a34a;
}

مشکل رایج :invalid: قرمز شدن قبل از این‌که کاربر چیزی تایپ کنه

مشکل اصلی :invalid اینه که یه input خالی با required، از همون لحظه‌ی اول (قبل از این‌که کاربر اصلاً چیزی تایپ کنه) invalid حساب می‌شه؛ چون خالی بودن، شرط required رو نقض می‌کنه. نتیجه این می‌شه که کل فرم از همون ابتدا قرمز به نظر می‌رسه، که تجربه‌ی بدیه:

/* بد: همه‌چیز از همون اول قرمزه */
input:invalid {
  border-color: red;
}

راه‌حل رایج، ترکیب کردن با :not(:placeholder-shown) هست؛ یعنی «فقط وقتی invalid باشه و دیگه placeholder نمایش داده نشه» (که یعنی کاربر حداقل یه چیزی تایپ کرده یا فیلد رو لمس کرده):

input:invalid:not(:placeholder-shown) {
  border-color: #dc2626;
}

یه راه‌حل جدیدتر و تمیزتر هم :user-invalid هست (که در بعضی مرورگرها با نام :user-error هم دیده می‌شه)؛ این pseudo-class فقط بعد از این‌که کاربر واقعاً با فیلد تعامل داشته (و ازش خارج شده)، فعال می‌شه:

input:user-invalid {
  border-color: #dc2626;
}

پشتیبانی مرورگرها از :user-invalid هنوز به‌اندازه‌ی :invalid گسترده نیست، پس ترکیب :invalid:not(:placeholder-shown) همچنان یه انتخاب امن‌تر برای پروژه‌هاییه که نیاز به پشتیبانی گسترده‌تر دارن.

:required و :optional

می‌تونید مستقیم بر اساس اجباری یا اختیاری بودن یه فیلد هم استایل بدید، مثلاً برای نشون دادن یه ستاره کنار فیلدهای اجباری:

label:has(+ input:required)::after {
  content: " *";
  color: #dc2626;
}

این مثال از combinator + (که تو فصل Selectors دیدیم) همراه با :has() استفاده می‌کنه تا یه ستاره‌ی قرمز کنار label هر input اجباری اضافه کنه، بدون این‌که مجبور باشید دستی تو HTML یه span یا کلاس اضافه اضافه کنید.

:in-range و :out-of-range

برای input هایی مثل type="number" که min و max دارن، می‌تونید بر اساس این‌که مقدار وارد‌شده تو محدوده‌ست یا نه، استایل بدید:

input[type="number"]:out-of-range {
  border-color: #dc2626;
  background: #fef2f2;
}

نمایش پیام خطا با CSS خالص (بدون جاوااسکریپت)

یه ترفند جالب، نمایش یه پیام خطای سفارشی فقط با CSS، با ترکیب :invalid و یه المنت مجاور:

.error-message {
  display: none;
  color: #dc2626;
  font-size: 0.85rem;
}

input:invalid:not(:placeholder-shown) ~ .error-message {
  display: block;
}

اینجا از combinator ~ (خواهر و برادر عمومی) استفاده شده؛ یعنی هر .error-message که بعد از input (و در همون سطح) میاد، فقط وقتی input معتبر نباشه نمایش داده می‌شه.

نکته سئو: validation سمت CSS/HTML هیچ تأثیری روی سئو نداره، ولی چون بدون جاوااسکریپت اجرا می‌شه، سرعت اولیه‌ی تعامل کاربر با فرم رو بهتر می‌کنه؛ که غیرمستقیم روی معیارهای تجربه‌ی کاربری مثل Core Web Vitals می‌تونه اثر مثبت داشته باشه.

اشتباه رایج: تکیه‌ی کامل روی validation فقط با CSS

یه اشتباه مهم اینه که فکر کنید :invalid و پیام‌های CSS جایگزین کامل validation سمت سرور یا جاوااسکریپت هستن. validation با CSS فقط ظاهریه و به‌راحتی با غیرفعال کردن CSS یا دستکاری مستقیم درخواست، دور زده می‌شه. همیشه باید validation واقعی و امنیتی، سمت سرور هم انجام بشه؛ CSS فقط تجربه‌ی کاربری رو بهتر می‌کنه، نه امنیت داده رو تضمین می‌کنه.

جمع‌بندی

:focus-within اجازه می‌ده یه wrapper به فوکوس هر فرزندش واکنش نشون بده، نه فقط خود input. برای validation، ترکیب :invalid:not(:placeholder-shown) (یا :user-invalid در مرورگرهای جدیدتر) از قرمز شدن زودهنگام فیلدهای خالی جلوگیری می‌کنه. با ترکیب این pseudo-class ها و combinator هایی مثل ~ و :has()، می‌شه تجربه‌ی کاربری غنی‌ای برای فرم‌ها ساخت، کاملاً بدون جاوااسکریپت؛ ولی همیشه یادتون باشه این‌ها فقط لایه‌ی ظاهری‌ان، نه جایگزین validation واقعی.