Erişilebilir Hata Duyurusu

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

Sorun

Standart HTML5 doğrulaması, görsel geri bildirim (:invalid veya :user-invalid aracılığıyla) sağlar ancak aria-invalid gibi erişilebilirlik özellikleriyle otomatik olarak senkronize edilmez.

Standart :invalid stilini kullanırsanız ekran okuyucular, kullanıcı boş bir zorunlu alana sekme tuşuyla geçtiği anda "Geçersiz giriş" uyarısı verebilir. Hata, etkileşim gerçekleşmeden önce duyurulduğu için yardımcı teknolojileri kullanan kullanıcılar açısından rahatsız edici bir deneyim oluşturur.

Çözüm

Programatik durumun (aria-invalid="true"), görsel durumun (:user-invalid) uygulandığı anda uygulanmasını istiyoruz. :user-invalid, tarayıcının dahili "kullanıcı etkileşimli " işaretine dayandığından, standart etkileşim etkinlikleri sırasında bu seçicinin eşleştiğini kontrol etmek için JavaScript'i kullanabiliriz.

Daha fazla bilgi için MDN aria-invalid sayfasına bakın.

Uygulama Stratejisi

  1. Görsel Katman: Kenarlıkları/simgeleri göstermek için CSS :user-invalid kullanın.
  2. Erişilebilirlik Katmanı: Durumu Yardımcı Teknoloji'ye (AT) iletmek için aria-invalid ve aria-errormessage kullanın.
  3. Görsel ve Erişilebilirlik Katmanını Birleştirme: blur ve input etkinliklerini dinleyen, öğenin :user-invalid ile eşleşip eşleşmediğini kontrol eden ve ARIA özelliklerini buna göre güncelleyen basit bir JavaScript yardımcı programı oluşturun.

Uygulama Kılavuzu

1. HTML Yapısı

aria-errormessage (veya daha geniş destek için aria-describedby) kullanarak girişinizi hata mesajına bağlayın.

<form>
  <div class="field">
    <label for="email">Email</label>
    <input 
      type="email" 
      id="email" 
      required 
      aria-errormessage="email-error"
    >
    <span id="email-error" class="error-msg">
      Please enter a valid email address.
    </span>
  </div>
</form>

2. CSS

Yerel sözde sınıf :user-invalid kullanarak hata mesajının görünürlüğünü kontrol edin.

.error-msg {
  display: none;
  color: #d93025;
}

/* Show error message when input is user-invalid */
input:user-invalid ~ .error-msg {
  display: block;
}

/* Optional: Visual cues on the input itself */
input:user-invalid {
  border-color: #d93025;
}

3. JavaScript

"UserInvalidChanged" etkinliği olmadığından, durumu kontrol etmek için standart form etkinliklerini kullanın.

const updateAriaState = (event) => {
  const input = event.target;
  if (!input.matches?.('input, textarea, select')) return;

  // Check if the browser currently considers this input "user-invalid"
  const isUserInvalid = input.matches(':user-invalid');
  
  if (isUserInvalid) {
    input.setAttribute('aria-invalid', 'true');
  } else {
    input.removeAttribute('aria-invalid');
  }
};

// Listen on the document to handle dynamically added fields.
// 'blur' and 'focus' do not bubble, so we must use the capture phase (true).
document.addEventListener('blur', updateAriaState, true);
document.addEventListener('focus', updateAriaState, true);

// Also update on input if we've already shown the error, 
// so the error clears immediately when fixed.
document.addEventListener('input', (event) => {
  const input = event.target;
  if (!input.matches?.('input, textarea, select')) return;

  const hasAriaInvalid = input.hasAttribute('aria-invalid');
  const ariaInvalid = input.getAttribute('aria-invalid');
  if (hasAriaInvalid && ariaInvalid === 'true') {
    updateAriaState(event);
  }
});

Yedekleme ve Tarayıcı Desteği

Özellik Algılama

CSS ve JavaScript'te destek olup olmadığını kontrol edebilirsiniz.

JavaScript Kontrolü:

if (!CSS.supports('selector(:user-invalid)')) {
  // Fallback logic here
}

Yedek için CSS

Yedek mantığınızın yerel davranıştan görsel olarak ayırt edilemediğinden emin olmak için hata stillerinizi hem sözde sınıfa hem de yedek sınıfınıza uygulamanız gerekir.

/* Apply error styles to both native selector and fallback class */
input:user-invalid,
input.user-invalid-fallback {
  border-color: #d93025;
  background-color: #fce8e6;
}

/* Show error message for both cases */
input:user-invalid ~ .error-msg,
input.user-invalid-fallback ~ .error-msg {
  display: block;
}

Yedek Mantık

:user-invalid eksikse WeakMap kullanarak etkileşim durumunu manuel olarak izleyin.

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' && input.matches?.('form')) {
      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.matches?.('input, textarea, select')) 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 = () => {
    if (CSS.supports('selector(:user-invalid)')) return;

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

  return { init };
})();

// Initialize globally
UserInvalidFallback.init();

Dikkat edilmesi gereken diğer noktalar

  1. aria-live ve aria-errormessage:

    • aria-errormessage, girişi metne bağlar ancak ekran okuyucular, giriş göründüğünde bunu hemen duyurmayabilir (yalnızca girişe odaklanıldığında).
    • Hata göründüğünde (ör.bulanıklaştırmada) hemen duyuru yapılması gerekiyorsa hata mesajı kapsayıcısına role="alert" veya aria-live="polite" ekleyebilirsiniz. Ancak kullanıcı düzeltmek için alana odaklandığında "çift duyuru" yapılmasını önlemek için kapsamlı bir şekilde test edin.
  2. Uluslararasılaştırma:

    • Hata mesajınızın (#email-error) metin içeriğinin çevrildiğinden emin olun. Mantık aynıdır.