Validación
El kit no valida: tu código (o tu librería de formularios) decide cuándo un campo tiene un error y agrega los atributos. El CSS solo muestra el estado.
Agrega aria-invalid="true" al campo y el mensaje con form-feedback form-feedback--error. Conecta el mensaje con aria-describedby para que se lea junto al campo. aria-invalid aplica el estilo y avisa al lector de pantalla; la clase is-invalid también aplica el estilo, pero no avisa.
<div class="form-field"> <label class="form-label" for="email">Email</label> <input class="form-input" type="email" id="email" name="email" value="ana@" aria-invalid="true" aria-describedby="email-error"> <p class="form-feedback form-feedback--error" id="email-error">Ingresa un email válido, como ana@ejemplo.cl.</p></div><div class="form-field"> <label class="form-label" for="region">Región</label> <select class="form-select" id="region" name="region" aria-invalid="true" aria-describedby="region-error"> <option value="" selected>Elige tu región</option> <option value="rm">Metropolitana</option> <option value="v">Valparaíso</option> </select> <p class="form-feedback form-feedback--error" id="region-error">Elige una región.</p></div>export default function ValidacionError() { return ( <> <div className="form-field"> <label className="form-label" htmlFor="email">Email</label> <input className="form-input" type="email" id="email" name="email" defaultValue="ana@" aria-invalid="true" aria-describedby="email-error" /> <p className="form-feedback form-feedback--error" id="email-error">Ingresa un email válido, como ana@ejemplo.cl.</p> </div> <div className="form-field"> <label className="form-label" htmlFor="region">Región</label> <select className="form-select" id="region" name="region" aria-invalid="true" aria-describedby="region-error" defaultValue=""> <option value="">Elige tu región</option> <option value="rm">Metropolitana</option> <option value="v">Valparaíso</option> </select> <p className="form-feedback form-feedback--error" id="region-error">Elige una región.</p> </div> </> );}Campo válido
Sección titulada «Campo válido»is-valid y form-feedback--success confirman un dato, como un usuario disponible. Úsalo solo cuando aporte: no hace falta marcar cada campo correcto.
<div class="form-field"> <label class="form-label" for="usuario">Usuario</label> <input class="form-input is-valid" type="text" id="usuario" name="usuario" value="ana.perez" aria-describedby="usuario-ok"> <p class="form-feedback form-feedback--success" id="usuario-ok">El usuario está disponible.</p></div>export default function ValidacionCorrecto() { return ( <div className="form-field"> <label className="form-label" htmlFor="usuario">Usuario</label> <input className="form-input is-valid" type="text" id="usuario" name="usuario" defaultValue="ana.perez" aria-describedby="usuario-ok" /> <p className="form-feedback form-feedback--success" id="usuario-ok">El usuario está disponible.</p> </div> );}Con ícono
Sección titulada «Con ícono»Un SVG dentro de form-feedback se alinea con la primera línea del texto.
<div class="form-field"> <label class="form-label" for="telefono">Teléfono</label> <input class="form-input" type="tel" id="telefono" name="telefono" value="9 1234" autocomplete="tel" aria-invalid="true" aria-describedby="telefono-error"> <p class="form-feedback form-feedback--error" id="telefono-error"> <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="9" /><path d="M12 8v4M12 16h.01" /></svg> El teléfono debe tener 9 dígitos. </p></div>export default function ValidacionIcono() { return ( <div className="form-field"> <label className="form-label" htmlFor="telefono">Teléfono</label> <input className="form-input" type="tel" id="telefono" name="telefono" defaultValue="9 1234" autoComplete="tel" aria-invalid="true" aria-describedby="telefono-error" /> <p className="form-feedback form-feedback--error" id="telefono-error"> <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="9" /><path d="M12 8v4M12 16h.01" /></svg> El teléfono debe tener 9 dígitos. </p> </div> );}Checkbox y radio
Sección titulada «Checkbox y radio»aria-invalid="true" en form-check__input pinta el borde con el color de error. El mensaje va después del form-check, dentro del mismo form-field.
<div class="form-field"> <div class="form-check"> <input class="form-check__input" type="checkbox" id="privacidad" name="privacidad" required aria-invalid="true" aria-describedby="privacidad-error"> <label class="form-check__label" for="privacidad">Acepto la política de privacidad</label> </div> <p class="form-feedback form-feedback--error" id="privacidad-error">Debes aceptar la política para continuar.</p></div>export default function ValidacionChecks() { return ( <div className="form-field"> <div className="form-check"> <input className="form-check__input" type="checkbox" id="privacidad" name="privacidad" required aria-invalid="true" aria-describedby="privacidad-error" /> <label className="form-check__label" htmlFor="privacidad">Acepto la política de privacidad</label> </div> <p className="form-feedback form-feedback--error" id="privacidad-error">Debes aceptar la política para continuar.</p> </div> );}Formulario completo
Sección titulada «Formulario completo»Al enviar con errores, muestra un mensaje con role="alert" y lleva el foco al primer campo con error.
<form action="/contacto" method="post" novalidate> <div class="message message--danger" role="alert">Revisa los campos marcados antes de enviar.</div> <div class="form-field"> <label class="form-label" for="nombre">Nombre <span class="form-label__required" aria-hidden="true">*</span></label> <input class="form-input" type="text" id="nombre" name="nombre" value="Ana Pérez" autocomplete="name" required> </div> <div class="form-field"> <label class="form-label" for="email">Email <span class="form-label__required" aria-hidden="true">*</span></label> <input class="form-input" type="email" id="email" name="email" autocomplete="email" required aria-invalid="true" aria-describedby="email-error"> <p class="form-feedback" id="email-error">Ingresa tu email.</p> </div> <div class="form-field"> <label class="form-label" for="asunto">Asunto</label> <select class="form-select" id="asunto" name="asunto"> <option value="cotizacion">Cotización</option> <option value="soporte">Soporte</option> <option value="otro">Otro</option> </select> </div> <div class="form-field"> <label class="form-label" for="mensaje">Mensaje</label> <textarea class="form-input" id="mensaje" name="mensaje" rows="4"></textarea> </div> <div class="form-field"> <div class="form-check"> <input class="form-check__input" type="checkbox" id="copia" name="copia"> <label class="form-check__label" for="copia">Enviarme una copia</label> </div> </div> <button type="submit" class="btn btn--primary">Enviar</button></form>export default function ValidacionFormulario() { return ( <form action="/contacto" method="post" noValidate> <div className="message message--danger" role="alert">Revisa los campos marcados antes de enviar.</div> <div className="form-field"> <label className="form-label" htmlFor="nombre">Nombre <span className="form-label__required" aria-hidden="true">*</span></label> <input className="form-input" type="text" id="nombre" name="nombre" defaultValue="Ana Pérez" autoComplete="name" required /> </div> <div className="form-field"> <label className="form-label" htmlFor="email">Email <span className="form-label__required" aria-hidden="true">*</span></label> <input className="form-input" type="email" id="email" name="email" autoComplete="email" required aria-invalid="true" aria-describedby="email-error" /> <p className="form-feedback" id="email-error">Ingresa tu email.</p> </div> <div className="form-field"> <label className="form-label" htmlFor="asunto">Asunto</label> <select className="form-select" id="asunto" name="asunto"> <option value="cotizacion">Cotización</option> <option value="soporte">Soporte</option> <option value="otro">Otro</option> </select> </div> <div className="form-field"> <label className="form-label" htmlFor="mensaje">Mensaje</label> <textarea className="form-input" id="mensaje" name="mensaje" rows="4" /> </div> <div className="form-field"> <div className="form-check"> <input className="form-check__input" type="checkbox" id="copia" name="copia" /> <label className="form-check__label" htmlFor="copia">Enviarme una copia</label> </div> </div> <button type="submit" className="btn btn--primary">Enviar</button> </form> );}Accesibilidad
Sección titulada «Accesibilidad»- No dependas solo del color: el texto del mensaje debe explicar qué falta y cómo corregirlo.
- Usa
aria-invalid="false"(o quita el atributo) cuando el campo se corrige. - Valida al salir del campo o al enviar, no con cada tecla.
- Si usas
novalidateen el formulario, el navegador no muestra sus propios avisos y tu código se encarga de todo. - Con los colores por defecto, el mensaje de error tiene un contraste de 5,1:1 y el de éxito, 5:1. El borde de campo válido (
--ds-color-success) tiene 3,4:1.
| Clase o atributo | Uso |
|---|---|
aria-invalid="true" |
Error en form-input, form-select o form-check__input: estilo y aviso al lector. |
is-invalid |
Error solo visual. |
is-valid |
Campo válido en form-input y form-select. |
form-feedback |
Mensaje de validación. Sin modificador se ve como error. |
form-feedback--error, form-feedback--success |
Mensaje de error o de éxito. |
Variables
Sección titulada «Variables»| Variable | Valor por defecto | Uso |
|---|---|---|
--ds-form-feedback-color | var(--ds-color-danger) | Color del texto y del ícono. |
--ds-form-feedback-font-size | var(--ds-font-size-sm) | |
--ds-form-feedback-gap | 0.375rem | Separación entre ícono y texto. |
Los colores del borde con error o válido están en --ds-form-input-invalid-color, --ds-form-input-valid-color y --ds-form-check-invalid-color.