Zorunlu Alan Geri Bildirimi

Web özellikleri: :user-valid ve :user-invalid

Sorun

Sayfa yüklendiğinde zorunlu alanları hemen hata durumuyla işaretlemek kafa karıştırıcı olabilir. İdeal olarak, zorunlu bir alan yalnızca kullanıcı doldurmaya çalışıp başarısız olduğunda "geçersiz" görünmelidir.

Çözüm

:user-invalid sözde sınıfı bu sorunu mükemmel bir şekilde çözer. Zorunlu bir alan, sayfa yüklendiğinde eşleşmez. Yalnızca aşağıdaki durumlarda eşleşir:

  1. Kullanıcı alanla etkileşimde bulunur (ör. bir karakter yazıp siler) ve ardından alanı boş bırakarak ayrılır (bulanıklaştırır).
  2. Kullanıcı, alan boşken formu göndermeye çalışır.

Uygulama Stratejisi

  1. HTML kısıtlaması: Girişlerinize required özelliğini ekleyin.
  2. Görsel geri bildirim: Kenarlığı kırmızı renkte biçimlendirmek ve "Zorunlu" yardımcı metnini göstermek için :user-invalid kullanın.
  3. Zamanlama: Görsel geri bildirim için tarayıcının yerel zamanlamasını kullanın. touched sınıfı eklemek için artık onBlur işleyicilerine ihtiyacınız yoktur. Ancak ARIA özelliklerini senkronize etmek için yine de JavaScript gerekir (aşağıya bakın).

Uygulama Kılavuzu

1. HTML Yapısı

<form id="feedback-form">
  <div class="field">
    <label for="full-name">Full Name</label>
    <input
      type="text"
      id="full-name"
      name="full-name"
      required
      aria-errormessage="name-error"
    >
    <!-- MANDATORY: Include an icon or distinct non-color indicator alongside error text -->
    <div id="name-error" class="error-msg">
      <span aria-hidden="true">❌</span> This field is required.
    </div>
  </div>
</form>

2. CSS

.error-msg {
  display: none;
  color: #d93025;
  font-size: 0.875rem;
  margin-top: 0.25rem;
}

/*
  Only highlight empty required fields AFTER the user visits them.
  MANDATORY: Provide multiple indicators (border shift + helper text/icon) to avoid color-only state communication.
*/
input:user-invalid {
  border-color: #d93025;
  background-color: #fce8e6;
}

input:user-invalid + .error-msg {
  display: block;
}

/* Optional: Subtle indicator for required fields that are valid */
input:required:user-valid {
  border-color: #188038;
  border-width: 2px;
}

3. JavaScript Durum Senkronizasyonu

ZORUNLU: :user-invalid görsel bir durum olduğundan, kullanıcı geçersiz bir alanı bulanıklaştırdığında veya göndermeye çalıştığında yardımcı teknolojiler için aria-invalid="true" değerini dinamik olarak senkronize edecek bir JavaScript köprüsü sağlamanız ZORUNLUDUR.

const form = document.getElementById('feedback-form');

const syncAriaInvalid = (input) => {
  if (!input.checkValidity()) {
    input.setAttribute('aria-invalid', 'true');
  } else {
    input.removeAttribute('aria-invalid');
  }
};

// Sync on blur when a user finishes interacting
form.addEventListener('blur', (e) => {
  if (e.target.matches('input[required]')) {
    syncAriaInvalid(e.target);
  }
}, true);

// Sync all required fields when submission is attempted
form.addEventListener('submit', () => {
  form.querySelectorAll('input[required]').forEach(syncAriaInvalid);
});

// Remove error state immediately upon correction
form.addEventListener('input', (e) => {
  if (e.target.matches('input[required]') && e.target.checkValidity()) {
    e.target.removeAttribute('aria-invalid');
  }
});

Yedekleme ve Tarayıcı Desteği

:user-valid ve :user-invalid için yedekler ve tarayıcı desteği

Yedek için CSS

input:user-invalid,
input.user-invalid-fallback {
  border-color: #d93025;
  background-color: #fce8e6;
}

input:user-invalid + .error-msg,
input.user-invalid-fallback + .error-msg {
  display: block;
}

JavaScript Yedek

WeakMap kullanarak etkileşim durumunu izleyen yeniden kullanılabilir bir yardımcı program kullanın. Bu sayede DOM'un "kirli" sınıflar veya veri özellikleri ile kirlenmesi önlenir.

const UserInvalidFallback = (() => {
  const dirtyState = new WeakMap();

  const updateState = (input) => {
    const isValid = input.checkValidity();

    // Update both visual and ARIA state
    input.classList.toggle('user-invalid-fallback', !isValid);
    input.classList.toggle('user-valid-fallback', isValid);

    if (!isValid) {
      input.setAttribute('aria-invalid', 'true');
    } else {
      input.removeAttribute('aria-invalid');
    }
  };

  const handleEvent = (event) => {
    const input = event.target;

    if (event.type === 'reset') {
      const controls = input.elements || [];
      for (const control of controls) {
        dirtyState.delete(control);
        control.classList.remove('user-invalid-fallback');
        control.classList.remove('user-valid-fallback');
        control.removeAttribute('aria-invalid');
      }
      return;
    }

    if (!input.checkValidity) return;

    if (event.type === 'input' || event.type === 'change') {
      const state = dirtyState.get(input) || { hasInteracted: false, hasBlurred: false };
      state.hasInteracted = true;
      dirtyState.set(input, state);
      if (state.hasBlurred) {
        updateState(input);
      }
    } else if (event.type === 'blur') {
      const state = dirtyState.get(input) || { hasInteracted: false, hasBlurred: false };
      state.hasBlurred = true;
      dirtyState.set(input, state);
      if (state.hasInteracted) {
        updateState(input);
      }
    }
  };

  const init = (root = document) => {
    if (CSS.supports('selector(:user-invalid)')) return;

    root.addEventListener('blur', handleEvent, true); // Capture phase
    root.addEventListener('input', handleEvent);
    root.addEventListener('change', handleEvent);
    root.addEventListener('reset', handleEvent, true); // Capture resets
  };

  return { init };
})();

// Initialize for a specific form
const form = document.querySelector('#demo-form');
UserInvalidFallback.init(form);

Dikkat edilmesi gereken diğer noktalar

  1. Yıldız işaretleri: Kullanıcıların etkileşimde bulunmadan önce ne bekleyeceklerini bilmeleri için zorunlu alanları etikette görsel olarak (ör. yıldız işaretiyle *) belirtmek hâlâ en iyi uygulamadır.
  2. Gönder düğmeleri: disabled düğmelerinin aksine, gönder düğmesini etkin durumda tutun. Kullanıcı bu düğmeyi tıklarsa tarayıcı, tüm boş zorunlu alanlarda otomatik olarak :user-invalid işlemini tetikler ve ilk alana odaklanır. Bu, erişilebilirlik ve kullanıcı deneyimi açısından mükemmeldir.
  3. Erişilebilirlik: Yerel :user-invalid, ARIA özellikleriyle otomatik olarak senkronize edilmez. aria-invalid öğesini görsel durumla senkronize tutmak için aşağıdaki JavaScript'i ekleyin:
// Sync aria-invalid with the CSS :user-invalid state
const syncAria = (el) => {
  el.setAttribute?.('aria-invalid', el.matches(':user-invalid') ? 'true' : 'false');
};

// Update on blur (to show error) and input (to clear it)
document.addEventListener('blur', (e) => syncAria(e.target), true);
document.addEventListener('input', (e) => {
  if (e.target.hasAttribute('aria-invalid')) syncAria(e.target);
});