✨Validación de contraseñas accessible✨

micaavigliano

Mica

Posted on December 5, 2023

✨Validación de contraseñas accessible✨

En esta entrada vamos a aprender como hacer una validación de contraseñas accesible.
El repo de Github podes encontrarlo en el siguiente link: https://github.com/micaavigliano/password-a11y
El demo de la applicación podes probarlo en el siguiente link: https://password-a11y.vercel.app/

Primero que nada, debemos crear nuestro input accesible

<form onSubmit={handleSubmit} id="form-id" className="pb-2 pt-6">
  <label htmlFor="password-input-id">Contraseña</label>
  <div
    className="rounded-full border-solid border-2 border-white px-3 py-1 flex flex-row justify-between"
    aria-label="this component include an input and a button to see the password"
  >
    <input
      type={seePassword ? "text" : "password"}
      onChange={(e) => {
        const newPassword = e.target.value;
        if (newPassword !== "") {
          setPassword(newPassword);
        } else if (newPassword === "") {
          setIsDirty(null);
        }
      }}
      placeholder="Escribí tu contraseña"
      autoComplete="new-password"
      className="text-white bg-transparent placeholder:text-slate-400 w-11/12"
      id="password-input-id"
      aria-describedby="password-requirement"
      required
      ref={inputRef}
    />
    <p
      id="password-requirement"
      className="absolute w-1 h-1 -m-1 overflow-hidden clip-hidden"
    >
      tu contraseña debe incluir {passwordRequirements}
    </p>
    <button
      onClick={() => {
        setSeePassword(!seePassword);
      }}
      role="switch"
      aria-checked={seePassword}
      aria-label={
        seePassword ? `esconder contraseña` : `mostrar contraseña`
      }
      className="hover:shadow-lg hover:bg-pink-300 hover:text-black"
    >
      {seePassword ? <VisibilityOff /> : <Visibility />}
    </button>
  </div>
</form>
Enter fullscreen mode Exit fullscreen mode

1) Para que nuestro input sea accesible debemos asociarlo con su label. Para que esto suceda debemos asociar el atributo htmlFor con el id del input. Al hacer esto, logramos cumplir con cuatro estandares de accesibilidad de la WCAG 2.2:

  1. Info and Relationships (nivel A): se enfoca en asegurar que la información este claramente asociada a su contexto y estructura.
  2. Name, Role, Value (nivel A): se enfoca que los elementos interactivos tengan nombres descriptivos, roles claros y valores significativos asociados a ellos. Esto ayuda a que las tecnologías asistivas sepan interpretarlos fácilmente.
  3. Non-text Content (nivel A): se enfoca en proporcionar alternativas textuales a contenido no textual (imagenes, inputs, gráficos, etc). Esto ayuda a que las tecnologías asistivas sepan interpretarlos fácilmente.
  4. Labels or Instructions (nivel A): se enfoca en asegurar que los formularios y controles interactivos tengan instrucciones claras.

2) agregar atributo autocomplete con el valor "new-password" para que el browser sepa que no se debe sugerir ni autocompletar el input con ninguna contraseña previa que el usuario haya utilizado previamente. Por otro lado, también permite notificarle a la tecnología asistiva que utilice el usuario que ese input esta "asegurado"

Captura de pantalla de un input con el cuadro de texto de voiceover que anuncia

3) En este caso, el atributo type va a variar entre text y password. Esto se debe a que vamos a tener un botón para poder visualizar la contraseña. Este mecanismo es de vital importancia en los inputs de contraseña ya que los screen readers NO anuncian la contraseña cuando tienen un type="password" pero sí lo hacen cuando tienen un type="text", por este motivo el botón para visualizar la contraseña va a modificar el estado de visualización.

  • ejemplo anuncio con type="password"

Captura de pantalla del anuncio de voiceover donde se lee

  • ejemplo de anuncio con type="text"

Captura de pantalla del anuncio de voiceover donde se lee

Pasemos a entender la funcionalidad de este input

1) Aquí vamos a encontrar el atributo aria-describedby con el valor password-requirement. El aria-describedby nos permite proporcionar información mucho más detallada o adicional relacionada a un elemento. En formularios se utiliza para asociar mensajes de validación o instrucciones adicionales a campos específicos, lo que sería nuestro caso.
Este atributo lo asociaremos mediante un atributo id al siguiente elemento:

<p
  id="password-requirement"
  className="absolute w-1 h-1 -m-1 overflow-hidden clip-hidden"
>
  tu contraseña debe incluir {passwordRequirements}
</p>
Enter fullscreen mode Exit fullscreen mode

Este elemento nos renderizará los requerimientos necesarios (en este caso en particular, pueden variar según las necesidades del input) para generar una contraseña segura y robusta. Por otro lado, nos va a permitir avisarle a la tecnología asistiva que hay información extra asociada a ese input y debe ser anunciada. En este caso va a anunciarlo de la siguiente manera:

captura de pantalla del screen reader voiceover que anuncia

2) También vamos a crear una referencia para que el elemento en cuestión sea lo primero que reciba foco apenas se renderiza la aplicación. De esta manera, el screen reader puede anunciar inmediatamente los requerimientos necesarios para generar una contraseña.

useEffect(() => {
  inputRef.current?.focus();
}, []);
Enter fullscreen mode Exit fullscreen mode

Funcionalidad botón para mostrar/esconder contraseña

1) Es necesario que este botón tenga un atributo role con el valor switch para anunciar que es un elemento que puede ser alternado entre dos valores. En conjunto al role="switch" tendremos que colocar un aria-checked.

2) Es de vital importancia actualizar el contenido del aria-label para que el usuario tenga contexto de cuál es la acción del botón.

Validación en tiempo real de los requisitos

Si el usuario de tecnologías asistivas quiere volver a escuchar los requisitos puede simplemente presionar Control-Option-Command-Slash en VoiceOver o Control+alt+n en NVDA. A su vez, nosotros podemos proveer un mecanismo en tiempo real para que sepa cuántos requisitos lleva validados de la siguiente manera:

const [total, setTotal] = useState(requirement.length);
const checkIfAllAreChecked = useCallback(
  (requirement: Requirement[]) => {
    let fulfilledRequirements = 0;
    if (Array.isArray(requirement)) {
      requirement.forEach((req: Requirement) => {
        const matchResult = password?.match(req.matchRegex);
        setIsDirty(true);
        //cada vez que un resultado matchea con nuestro regex va a incrementar nuestro counter
        if (matchResult) {
          fulfilledRequirements++;
        }
     });
     setTotal(requirement.length - fulfilledRequirements);
    }
  },
 [password, setIsDirty]
);
{...}
<div
  role="status"
  aria-live="polite"
  aria-atomic="true"
  className="absolute w-1 h-1 -m-1 overflow-hidden clip-hidden"
  id={idInput}
>
  {total === 0 ? (
    <p>tu contraseña esta lista!</p>
  ) : (
    <p>
      {total} requisitos de {requirement.length}
    </p>
  )}
</div>
Enter fullscreen mode Exit fullscreen mode

El mensaje se anunciará de la siguiente manera:

Captura de pantalla del anuncio de voiceover donde se puede leer

Para que esto pueda ser anunciado como una actualización debemos utilizar el role="status", en conjunto de dos atributos aria-live y aria-atomic.
El atributo aria-live="polite" sirve para saber cómo y cuando anunciar las actualizaciones a los usuarios a través de los lectores de pantalla. En este caso, el valor polite indica que las actualizaciones se anunciarán una vez que el usuario haya terminado su actividad actual. Esto significa que el lector de pantalla esperará para anunciar la actualización.
El atributo aria-atomic="true" sirve para indicarle al lector de pantalla que el contenido debe ser anunciado por completo.

Con esta breve explicación doy por finalizado este post! No obstante, no se olviden de que en el repo van a encontrar el código completo y cualquier duda o recomendación para futuros posts me las pueden hacer por privado por cualquiera de estos medios:

Linkedin: https://www.linkedin.com/in/micaelaavigliano/
Github: https://github.com/micaavigliano
Twitter: https://twitter.com/messycatx

Muchas gracias por leer la tercera entrada!🩷

💖 💪 🙅 🚩
micaavigliano
Mica

Posted on December 5, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related