Los navegadores ejecutan la validación de formularios de forma nativa; Luego, la mayoría de los equipos vuelven a implementar sus imágenes en JavaScript. CSS puede diseñar la maquinaria directamente, lo que se adapta perfectamente a un sitio estático.

/* touched, wrong */
input:user-invalid { border-color: #bb0000; }
/* untouched but already empty by design */
input:placeholder-shown { border-color: #d9d9d9; }
/* the whole form is submittable */
form:has(:invalid) .save { opacity: .4; pointer-events: none; }

Componer los estados honestamente

  1. Valor incorrecto: :user-invalid, por lo que los campos nunca gritan antes de que el lector los toque.
  2. Formulario listo para enviar: form:has(:invalid) desactiva el botón de guardar hasta que cada campo obligatorio pase.
  3. Vacío versus error: proporcione a las entradas un atributo de marcador de posición para que: marcador de posición mostrado separe "vacío" de "no válido".
  4. Texto de error: empareje el estilo con un mensaje visible; un borde rojo por sí solo no supera todas las comprobaciones.

Mantén la semántica real: el navegador también bloquea el submit de forma nativa, así que el CSS pinta encima del comportamiento ya juzgado, no lo sustituye. El soporte de :has() según caniuse medía el 95,91 por ciento en este repositorio, y el formulario sigue validando bien sin él.