Validación de formularios y contador (form, counter)
Dos componentes pequeños que trabajan sobre los atributos que ya escribes. El componente form convierte la validación de restricciones del navegador en mensajes en línea, un resumen y un foco sensato, con mensajes que controlas por tipo de error. El counter muestra caracteres o palabras junto a un campo de texto y puede marcarlo inválido cuando se supera un límite blando. Sin JavaScript el navegador valida y maxlength limita el texto como siempre.
<form class="iv-form" data-iv-component="form" data-iv-summary="true" data-iv-validate-on="blur" action="#" method="get" style="max-width: 34rem" aria-describedby="v-note">
<p class="iv-u-text-sm iv-u-text-muted iv-u-m-0" id="v-note">Demo form with made-up fields. Nothing is sent to a server: a valid submit only reloads this page.</p>
<div class="iv-field">
<label class="iv-label" for="v-name">Full name <span class="iv-label__required" aria-hidden="true">*</span></label>
<input class="iv-input" id="v-name" name="name" type="text" required minlength="3" autocomplete="name"
data-iv-error-value-missing="Enter the name that should appear on the invoice."
data-iv-error-too-short="Use at least 3 characters, so the name can be matched.">
</div>
<div class="iv-field">
<label class="iv-label" for="v-email">Work email <span class="iv-label__required" aria-hidden="true">*</span></label>
<input class="iv-input" id="v-email" name="email" type="email" required autocomplete="email" aria-describedby="v-email-help"
data-iv-error-value-missing="Enter an email address we can reply to."
data-iv-error-type-mismatch="Write the whole address, like [email protected].">
<p class="iv-field__help iv-u-m-0" id="v-email-help">Used only for this demo; it is never stored.</p>
</div>
<div class="iv-field">
<label class="iv-label" for="v-site">Website</label>
<input class="iv-input" id="v-site" name="site" type="url" placeholder="https://example.com" autocomplete="url"
data-iv-error-type-mismatch="Start the address with https:// so it can be opened.">
</div>
<div class="iv-field">
<label class="iv-label" for="v-age">Age <span class="iv-label__required" aria-hidden="true">*</span></label>
<input class="iv-input" id="v-age" name="age" type="number" required min="18" max="120" inputmode="numeric"
data-iv-error-value-missing="Enter your age in years."
data-iv-error-range-underflow="This demo is for people aged 18 or over."
data-iv-error-range-overflow="Enter an age of 120 or less.">
</div>
<div class="iv-field">
<label class="iv-label" for="v-start">Start date <span class="iv-label__required" aria-hidden="true">*</span></label>
<input class="iv-input" id="v-start" name="start" type="date" required min="2026-10-01"
data-iv-error-value-missing="Pick the day the plan should start."
data-iv-error-range-underflow="Pick a day from 1 October 2026 onwards.">
</div>
<div class="iv-field">
<label class="iv-label" for="v-plan">Plan <span class="iv-label__required" aria-hidden="true">*</span></label>
<select class="iv-select" id="v-plan" name="plan" required
data-iv-error-value-missing="Choose one of the three plans.">
<option value="">Choose a plan</option>
<option value="starter">Starter</option>
<option value="team">Team</option>
<option value="enterprise">Enterprise</option>
</select>
</div>
<div class="iv-field">
<label class="iv-label" for="v-pass">Password <span class="iv-label__required" aria-hidden="true">*</span></label>
<input class="iv-input" id="v-pass" name="password" type="password" required pattern="[A-Za-z0-9]{8,}" autocomplete="new-password"
data-iv-valid-sample="Bilbao2026"
aria-describedby="v-pass-help"
data-iv-error-value-missing="Choose a password for the demo account."
data-iv-error-pattern-mismatch="Use 8 characters or more, letters and digits only.">
<p class="iv-field__help iv-u-m-0" id="v-pass-help">Made-up account: the value never leaves the page.</p>
</div>
<div class="iv-field">
<label class="iv-check">
<input type="checkbox" id="v-terms" name="terms" required
data-iv-error-value-missing="Tick the box to accept the demo terms.">
I accept the terms of this demo
</label>
</div>
<div class="iv-cluster">
<button class="iv-button iv-button--primary" type="submit">Create account (demo)</button>
<button class="iv-button iv-button--ghost" type="reset">Reset</button>
</div>
</form>// data-iv-component="form"
{
summary: true,
validateOn: "blur",
errorValueMissing: "Enter the name that should appear on the invoice.",
errorTooShort: "Use at least 3 characters, so the name can be matched.",
errorTypeMismatch: "Write the whole address, like [email protected].",
errorRangeUnderflow: "This demo is for people aged 18 or over.",
errorRangeOverflow: "Enter an age of 120 or less.",
validSample: "Bilbao2026",
errorPatternMismatch: "Use 8 characters or more, letters and digits only.",
}Mensajes
Cada control puede llevar un mensaje por tipo de error: data-iv-error-value-missing, -type-mismatch, -pattern-mismatch, -too-short, -too-long, -range-underflow, -range-overflow, -step-mismatch, -bad-input y -custom, o un único data-iv-error. Sin ninguno de ellos se usa el mensaje del navegador. Para añadir reglas propias escucha iv:validate y llama a detail.setError(mensaje); una cadena vacía lo limpia.
Opciones
| Opción | Atributo | Por defecto | Efecto |
|---|---|---|---|
validateOn | data-iv-validate-on | blur | blur valida un campo al perder el foco y después en cada entrada mientras sea inválido; input valida en cada pulsación; submit solo al enviar. |
summary | data-iv-summary | false | Pinta un resumen de errores con enlaces a los campos al principio del formulario. |
summaryTitle | data-iv-summary-title | Please fix the following | Título del resumen. |
focusFirst | data-iv-focus-first | true | Lleva el foco al primer campo inválido (o al resumen) tras un envío fallido. |
scroll | data-iv-scroll | true | Desplaza el campo enfocado a la vista. |
live | data-iv-live | true | Anuncia los mensajes de error con aria-live. |
Métodos y eventos
validate() devuelve si el formulario es válido y aplica todos los estados, validateField(control), reset(), destroy(); lectura de errors. Eventos: iv:validate en cada control (detail.control, message, setError), iv:invalid en el formulario tras un envío fallido (detail.errors), e iv:valid (cancelable) justo antes de que un envío válido siga adelante, que es donde toma el relevo un envío asíncrono.
Contador
Pon data-iv-component="counter" en el contenedor del campo. El recuento sigue a maxlength o a data-iv-max; con un data-iv-max blando se puede seguir escribiendo, el contador pasa a su estado «over» y el control se vuelve inválido mediante setCustomValidity, así que el componente de formulario y el navegador lo notifican los dos.
<div class="iv-stack" style="max-width: 34rem">
<p class="iv-u-text-sm iv-u-text-muted iv-u-m-0">Two demo fields with made-up text. Nothing is submitted.</p>
<div class="iv-field" data-iv-component="counter" data-iv-mode="chars">
<label class="iv-label" for="c-bio">Short bio</label>
<textarea class="iv-textarea" id="c-bio" name="bio" maxlength="280" aria-describedby="c-bio-help" placeholder="A couple of lines about your work">Product designer in Bilbao. I draw interfaces that stay readable at three in the morning, and I like forms that say what is wrong instead of turning red and staying quiet about it. Currently writing about design systems.</textarea>
<p class="iv-field__help iv-u-m-0" id="c-bio-help">Hard limit: the browser stops typing at 280 characters, with or without JavaScript.</p>
</div>
<div class="iv-field" data-iv-component="counter" data-iv-mode="words" data-iv-max="40" data-iv-template="{count} of {max} words · {remaining} left" data-iv-over-text="Trim the pitch to 40 words or fewer.">
<label class="iv-label" for="c-pitch">Pitch</label>
<textarea class="iv-textarea" id="c-pitch" name="pitch" aria-describedby="c-pitch-help">We build small tools for teams that ship on Fridays. This pitch runs past the limit on purpose, so the counter turns red, the control is marked invalid and the form refuses to send it until the text is shorter than the agreed limit.</textarea>
<p class="iv-field__help iv-u-m-0" id="c-pitch-help">Soft limit: you can type past 40 words, and the field is then marked invalid until you trim it.</p>
</div>
</div>// data-iv-component="counter"
{
mode: "chars",
max: 40,
template: "{count} of {max} words · {remaining} left",
overText: "Trim the pitch to 40 words or fewer.",
}| Opción | Atributo | Por defecto | Efecto |
|---|---|---|---|
mode | data-iv-mode | chars | chars o words. |
max | data-iv-max | maxlength | Límite que se muestra y, cuando no hay maxlength, se exige como error de validación. |
warnAt | data-iv-warn-at | 0.9 | Fracción del límite a partir de la cual el contador pasa a su estado de aviso. |
template | data-iv-template | {count} / {max} | Texto del contador; también está disponible {remaining}. |
overText | data-iv-over-text | Too long | Mensaje de validación cuando se supera un límite blando. |
Tecnología asistiva
Los controles inválidos llevan aria-invalid y están descritos por su mensaje; el resumen es una región viva y cada entrada es un enlace a su campo; el contador se anuncia con cortesía. Con movimiento reducido se omite la sacudida del envío fallido.
Sin JavaScript
El navegador valida con sus propios globos y maxlength recorta el texto. Los límites blandos y el recuento de palabras necesitan JavaScript; nada más lo necesita.
Errores frecuentes
- Validar solo en JavaScript: conserva los atributos HTML, ellos son las reglas.
- Un resumen sin
focusFirst: quien usa una lupa de pantalla puede no verlo nunca. - Mensajes que describen la regla en vez de la solución: escribe «Indica una fecha posterior a hoy», no «Fecha no válida».