Seçim Menüsü Etkileşimi

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

Sorun

Zorunlu açılır listelerde (ör. "Ülke seçin"), varsayılan seçeneğin değeri boşsa standart doğrulama, alanı hemen geçersiz olarak işaretler. Bu durum görsel gürültüye neden olabilir. Hatayı yalnızca kullanıcı menüyü açıp bir seçenek belirlemeden kapattığında veya formu göndermeye çalıştığında göstermek istiyoruz.

Çözüm

:user-invalid sözde sınıfı, <select> öğeleriyle sorunsuz bir şekilde çalışır. Kullanıcının etkileşim akışına saygı gösterir: Sayfayı yüklemek veya değişiklik yapmadan odaklanmak/bulanıklaştırmak etkileşim olarak sayılmaz. Bu nedenle, kullanıcı etkin bir şekilde seçim yapmaya çalışana kadar alan nötr kalır.

Uygulama Stratejisi

  1. HTML kısıtlaması: required ile <select> kullanın. İlk seçenekte value="" olmalı ve geçerli bir seçim yapılmasını zorunlu kılmak için ideal olarak devre dışı bırakılmalı/gizlenmelidir.
  2. Görsel Geri Bildirim: Seçim kutusu kenarlığını biçimlendirmek için :user-invalid kullanın.
  3. Zamanlama: Kullanıcı, denetimi bulanıklaştırmadan önce veya form gönderildiğinde değeri değiştirirse (varsayılan geçersiz duruma geri döndürse bile) tarayıcı, alanı "etkileşimde bulunuldu" olarak kabul eder.

Uygulama Kılavuzu

1. HTML Yapısı

Burada "yer tutucu" seçeneği önemlidir.

<form>
  <div class="field">
    <label for="country">Country</label>
    <select
      id="country"
      name="country"
      required
      aria-errormessage="country-error"
    >
      <option value="" disabled selected>Select a country...</option>
      <option value="us">United States</option>
      <option value="ca">Canada</option>
      <option value="uk">United Kingdom</option>
    </select>
    <div id="country-error" class="error-msg">
      Please select a country.
    </div>
  </div>
</form>

2. CSS

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

/*
  Only show error after the user visits the select menu.
*/
select:user-invalid {
  border-color: #d93025;
  background-color: #fce8e6;
}

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

select:user-valid {
  border-color: #188038;
}

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. Mobil davranış: Mobil cihazlarda "bulanıklaştırma", OS seçiciye bağlı olarak farklı şekilde gerçekleşebilir. Gerçek cihazlarda test yapılması önerilir.
  2. 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);
});