Form
Patrón de composición de campos de formulario: etiquetas, hints, errores inline y acciones.
1. Campo simple
Mostrar HTML
<svg aria-hidden="true" focusable="false" width="0" height="0" style="position:absolute;overflow:hidden">
<defs>
<symbol id="form-chk-check" viewBox="0 0 24 24">
<path d="M19.1744 7.00179C19.4702 7.29406 19.6364 7.69041 19.6364 8.10368C19.6364 8.51694 19.4702 8.91329 19.1744 9.20556L11.2847 16.9982C10.9887 17.2904 10.5875 17.4546 10.169 17.4546C9.75063 17.4546 9.34935 17.2904 9.05344 16.9982L5.89754 13.8812C5.6101 13.5872 5.45105 13.1935 5.45465 12.7849C5.45824 12.3763 5.6242 11.9854 5.91676 11.6964C6.20933 11.4074 6.60509 11.2435 7.01883 11.24C7.43256 11.2364 7.83115 11.3935 8.12876 11.6774L10.169 13.6926L16.9432 7.00179C17.2391 6.70961 17.6404 6.54547 18.0588 6.54547C18.4772 6.54547 18.8785 6.70961 19.1744 7.00179Z" fill="currentColor"/>
</symbol>
<symbol id="form-chk-mix" viewBox="0 0 14 3">
<path d="M12.3846 0C13.2768 0 14 0.671573 14 1.5C14 2.32843 13.2768 3 12.3846 3H1.61538C0.723232 3 0 2.32843 0 1.5C0 0.671573 0.723232 0 1.61538 0L12.3846 0Z" fill="currentColor"/>
</symbol>
<symbol id="form-error-icon" viewBox="0 0 3 15">
<path d="M1.50193 14.8389C1.91494 14.8389 2.26787 14.6975 2.56071 14.4146C2.85356 14.1317 3 13.7916 3 13.3944C3 12.9972 2.85291 12.6577 2.55879 12.3761C2.26466 12.0944 1.91109 11.9536 1.49807 11.9536C1.08506 11.9536 0.732128 12.095 0.439286 12.3779C0.146443 12.6608 0 13.0009 0 13.3981C0 13.7953 0.147085 14.1348 0.441214 14.4164C0.735343 14.6981 1.08891 14.8389 1.50193 14.8389ZM3 9.89262V0H0V9.89262H3Z" fill="currentColor"/>
</symbol>
</defs>
</svg>
<div class="gn-field">
<div class="gn-field__header">
<label class="gn-field__label" for="form-ex1-name">Nombre</label>
</div>
<div class="gn-input">
<input id="form-ex1-name" type="text" class="gn-input__control" placeholder="Escribe tu nombre">
</div>
</div>
2. Campo con hint
Mostrar HTML
<div class="gn-field">
<div class="gn-field__header">
<label class="gn-field__label" for="form-ex2-email">Correo electrónico</label>
<span class="gn-field__hint" id="form-ex2-hint">Te enviaremos la confirmación a esta dirección.</span>
</div>
<div class="gn-input">
<input id="form-ex2-email" type="email" class="gn-input__control" placeholder="nombre@ejemplo.es" aria-describedby="form-ex2-hint">
</div>
</div>
3. Campo obligatorio con hint
Mostrar HTML
<div class="gn-field">
<div class="gn-field__header">
<label class="gn-field__label" for="form-ex3-nif">
NIF<span class="gn-field__required" aria-hidden="true">*</span><span class="sr-only">obligatorio</span>
</label>
<span class="gn-field__hint" id="form-ex3-hint">Introduce el NIF sin espacios ni guiones.</span>
</div>
<div class="gn-input">
<input id="form-ex3-nif" type="text" class="gn-input__control" placeholder="12345678A" required aria-describedby="form-ex3-hint">
</div>
</div>
4. Campo inválido — input de texto
Error: El nombre debe tener al menos 2 caracteres.
Mostrar HTML
<div style="padding-inline-start: 20px;">
<div class="gn-field gn-field--invalid">
<div class="gn-field__header">
<label class="gn-field__label" for="form-ex4-name">
Nombre<span class="gn-field__required" aria-hidden="true">*</span><span class="sr-only">obligatorio</span>
</label>
<p class="gn-field__error" id="form-ex4-error">
<svg class="gn-field__error-icon" xmlns="http://www.w3.org/2000/svg" width="3" height="15" aria-hidden="true" focusable="false"><use href="#form-error-icon"></use></svg>
<span class="gn-field__error-prefix">Error:</span>
<span class="gn-field__error-text">El nombre debe tener al menos 2 caracteres.</span>
</p>
</div>
<div class="gn-input gn-input--error">
<input id="form-ex4-name" type="text" class="gn-input__control" value="J" aria-invalid="true" aria-describedby="form-ex4-error">
</div>
</div>
</div>
5. Campo inválido — select
Error: Selecciona una provincia.
Mostrar HTML
<div style="padding-inline-start: 20px;">
<div class="gn-field gn-field--invalid">
<div class="gn-field__header">
<label class="gn-field__label" for="form-ex5-province">
Provincia<span class="gn-field__required" aria-hidden="true">*</span><span class="sr-only">obligatorio</span>
</label>
<p class="gn-field__error" id="form-ex5-error">
<svg class="gn-field__error-icon" xmlns="http://www.w3.org/2000/svg" width="3" height="15" aria-hidden="true" focusable="false"><use href="#form-error-icon"></use></svg>
<span class="gn-field__error-prefix">Error:</span>
<span class="gn-field__error-text">Selecciona una provincia.</span>
</p>
</div>
<div class="gn-select gn-select--error">
<select id="form-ex5-province" class="gn-select__control" aria-invalid="true" aria-describedby="form-ex5-error" required>
<option value="" disabled selected hidden>Seleccionar</option>
<option value="na">Navarra</option>
<option value="bi">Bizkaia</option>
<option value="gi">Gipuzkoa</option>
</select>
</div>
</div>
</div>
6. Campo inválido — textarea con hint y error
Error: La descripción es demasiado corta.
Mostrar HTML
<div style="padding-inline-start: 20px;">
<div class="gn-field gn-field--invalid">
<div class="gn-field__header">
<label class="gn-field__label" for="form-ex6-desc">
Descripción<span class="gn-field__required" aria-hidden="true">*</span><span class="sr-only">obligatorio</span>
</label>
<span class="gn-field__hint" id="form-ex6-hint">Mínimo 20 caracteres.</span>
<p class="gn-field__error" id="form-ex6-error">
<svg class="gn-field__error-icon" xmlns="http://www.w3.org/2000/svg" width="3" height="15" aria-hidden="true" focusable="false"><use href="#form-error-icon"></use></svg>
<span class="gn-field__error-prefix">Error:</span>
<span class="gn-field__error-text">La descripción es demasiado corta.</span>
</p>
</div>
<div class="gn-textarea gn-textarea--error">
<textarea id="form-ex6-desc" class="gn-textarea__control" aria-invalid="true" aria-describedby="form-ex6-hint form-ex6-error" required>Texto corto.</textarea>
</div>
</div>
</div>
7. Grupo de radios
Mostrar HTML
<fieldset class="gn-field">
<div class="gn-field__header">
<legend class="gn-field__legend">Tipo de solicitud</legend>
</div>
<div class="gn-field__options">
<div class="gn-radio">
<input class="gn-radio__input" type="radio" id="form-ex7-r1" name="form-ex7-type" value="alta">
<label class="gn-radio__label" for="form-ex7-r1">
<span class="gn-radio__box" aria-hidden="true"><span class="gn-radio__dot"></span></span>
<span class="gn-radio__text">Alta</span>
</label>
</div>
<div class="gn-radio">
<input class="gn-radio__input" type="radio" id="form-ex7-r2" name="form-ex7-type" value="baja" checked>
<label class="gn-radio__label" for="form-ex7-r2">
<span class="gn-radio__box" aria-hidden="true"><span class="gn-radio__dot"></span></span>
<span class="gn-radio__text">Baja</span>
</label>
</div>
<div class="gn-radio">
<input class="gn-radio__input" type="radio" id="form-ex7-r3" name="form-ex7-type" value="modificacion">
<label class="gn-radio__label" for="form-ex7-r3">
<span class="gn-radio__box" aria-hidden="true"><span class="gn-radio__dot"></span></span>
<span class="gn-radio__text">Modificación</span>
</label>
</div>
</div>
</fieldset>
8. Grupo de radios con hint
Mostrar HTML
<fieldset class="gn-field" aria-describedby="form-ex8-hint">
<div class="gn-field__header">
<legend class="gn-field__legend">Canal de comunicación preferente</legend>
<span class="gn-field__hint" id="form-ex8-hint">Selecciona el canal por el que prefieres recibir las notificaciones.</span>
</div>
<div class="gn-field__options">
<div class="gn-radio">
<input class="gn-radio__input" type="radio" id="form-ex8-r1" name="form-ex8-channel" value="email">
<label class="gn-radio__label" for="form-ex8-r1">
<span class="gn-radio__box" aria-hidden="true"><span class="gn-radio__dot"></span></span>
<span class="gn-radio__text">Correo electrónico</span>
</label>
</div>
<div class="gn-radio">
<input class="gn-radio__input" type="radio" id="form-ex8-r2" name="form-ex8-channel" value="sms" checked>
<label class="gn-radio__label" for="form-ex8-r2">
<span class="gn-radio__box" aria-hidden="true"><span class="gn-radio__dot"></span></span>
<span class="gn-radio__text">SMS</span>
</label>
</div>
<div class="gn-radio">
<input class="gn-radio__input" type="radio" id="form-ex8-r3" name="form-ex8-channel" value="postal">
<label class="gn-radio__label" for="form-ex8-r3">
<span class="gn-radio__box" aria-hidden="true"><span class="gn-radio__dot"></span></span>
<span class="gn-radio__text">Correo postal</span>
</label>
</div>
</div>
</fieldset>
9. Grupo de radios con descripción por opción
Mostrar HTML
<fieldset class="gn-field" aria-describedby="form-ex9-hint">
<div class="gn-field__header">
<legend class="gn-field__legend">Régimen fiscal</legend>
<span class="gn-field__hint" id="form-ex9-hint">Selecciona el que corresponda a tu actividad.</span>
</div>
<div class="gn-field__options">
<div class="gn-radio">
<input class="gn-radio__input" type="radio" id="form-ex9-r1" name="form-ex9-regime" value="general">
<label class="gn-radio__label" for="form-ex9-r1">
<span class="gn-radio__box" aria-hidden="true"><span class="gn-radio__dot"></span></span>
<span class="gn-radio__content">
<span class="gn-radio__text">Régimen general</span>
<span class="gn-radio__description">Para empresas y autónomos con actividad habitual.</span>
</span>
</label>
</div>
<div class="gn-radio">
<input class="gn-radio__input" type="radio" id="form-ex9-r2" name="form-ex9-regime" value="simplificado" checked>
<label class="gn-radio__label" for="form-ex9-r2">
<span class="gn-radio__box" aria-hidden="true"><span class="gn-radio__dot"></span></span>
<span class="gn-radio__content">
<span class="gn-radio__text">Régimen simplificado</span>
<span class="gn-radio__description">Para actividades con módulos o estimación objetiva.</span>
</span>
</label>
</div>
<div class="gn-radio">
<input class="gn-radio__input" type="radio" id="form-ex9-r3" name="form-ex9-regime" value="especial">
<label class="gn-radio__label" for="form-ex9-r3">
<span class="gn-radio__box" aria-hidden="true"><span class="gn-radio__dot"></span></span>
<span class="gn-radio__content">
<span class="gn-radio__text">Régimen especial</span>
<span class="gn-radio__description">Para determinados sectores o colectivos profesionales.</span>
</span>
</label>
</div>
</div>
</fieldset>
10. Grupo de radios inválido
Mostrar HTML
<div style="padding-inline-start: 20px;">
<fieldset class="gn-field gn-fieldset--invalid" aria-describedby="form-ex10-hint form-ex10-error">
<div class="gn-field__header">
<legend class="gn-field__legend">Tipo de solicitud<span class="gn-field__required" aria-hidden="true">*</span><span class="sr-only">obligatorio</span></legend>
<span class="gn-field__hint" id="form-ex10-hint">Selecciona una opción.</span>
<p class="gn-field__error" id="form-ex10-error">
<svg class="gn-field__error-icon" xmlns="http://www.w3.org/2000/svg" width="3" height="15" aria-hidden="true" focusable="false"><use href="#form-error-icon"></use></svg>
<span class="gn-field__error-prefix">Error:</span>
<span class="gn-field__error-text">Debes seleccionar una opción.</span>
</p>
</div>
<div class="gn-field__options">
<div class="gn-radio">
<input class="gn-radio__input" type="radio" id="form-ex10-r1" name="form-ex10-type" value="alta" aria-invalid="true">
<label class="gn-radio__label" for="form-ex10-r1">
<span class="gn-radio__box" aria-hidden="true"><span class="gn-radio__dot"></span></span>
<span class="gn-radio__text">Alta</span>
</label>
</div>
<div class="gn-radio">
<input class="gn-radio__input" type="radio" id="form-ex10-r2" name="form-ex10-type" value="baja" aria-invalid="true">
<label class="gn-radio__label" for="form-ex10-r2">
<span class="gn-radio__box" aria-hidden="true"><span class="gn-radio__dot"></span></span>
<span class="gn-radio__text">Baja</span>
</label>
</div>
<div class="gn-radio">
<input class="gn-radio__input" type="radio" id="form-ex10-r3" name="form-ex10-type" value="modificacion" aria-invalid="true">
<label class="gn-radio__label" for="form-ex10-r3">
<span class="gn-radio__box" aria-hidden="true"><span class="gn-radio__dot"></span></span>
<span class="gn-radio__text">Modificación</span>
</label>
</div>
</div>
</fieldset>
</div>
11. Grupo de checkboxes inválido con descripción por opción
Mostrar HTML
<div style="padding-inline-start: 20px;">
<fieldset class="gn-field gn-fieldset--invalid" aria-describedby="form-ex11-error">
<div class="gn-field__header">
<legend class="gn-field__legend">Canal de notificación<span class="gn-field__required" aria-hidden="true">*</span><span class="sr-only">obligatorio</span></legend>
<p class="gn-field__error" id="form-ex11-error">
<svg class="gn-field__error-icon" xmlns="http://www.w3.org/2000/svg" width="3" height="15" aria-hidden="true" focusable="false"><use href="#form-error-icon"></use></svg>
<span class="gn-field__error-prefix">Error:</span>
<span class="gn-field__error-text">Selecciona al menos una opción.</span>
</p>
</div>
<div class="gn-field__options">
<div class="gn-checkbox gn-checkbox--error">
<input class="gn-checkbox__input" type="checkbox" id="form-ex11-c1" name="form-ex11-channel" value="email" aria-invalid="true">
<label class="gn-checkbox__label" for="form-ex11-c1">
<span class="gn-checkbox__box" aria-hidden="true"><svg focusable="false" class="gn-checkbox__icon gn-checkbox__icon--check" aria-hidden="true"><use href="#form-chk-check"></use></svg></span>
<span class="gn-checkbox__content">
<span class="gn-checkbox__text">Correo electrónico</span>
<span class="gn-checkbox__description">Recibirás avisos en la dirección registrada.</span>
</span>
</label>
</div>
<div class="gn-checkbox gn-checkbox--error">
<input class="gn-checkbox__input" type="checkbox" id="form-ex11-c2" name="form-ex11-channel" value="sms" aria-invalid="true">
<label class="gn-checkbox__label" for="form-ex11-c2">
<span class="gn-checkbox__box" aria-hidden="true"><svg focusable="false" class="gn-checkbox__icon gn-checkbox__icon--check" aria-hidden="true"><use href="#form-chk-check"></use></svg></span>
<span class="gn-checkbox__content">
<span class="gn-checkbox__text">SMS</span>
<span class="gn-checkbox__description">Mensaje de texto al número de teléfono registrado.</span>
</span>
</label>
</div>
<div class="gn-checkbox gn-checkbox--error">
<input class="gn-checkbox__input" type="checkbox" id="form-ex11-c3" name="form-ex11-channel" value="push" aria-invalid="true">
<label class="gn-checkbox__label" for="form-ex11-c3">
<span class="gn-checkbox__box" aria-hidden="true"><svg focusable="false" class="gn-checkbox__icon gn-checkbox__icon--check" aria-hidden="true"><use href="#form-chk-check"></use></svg></span>
<span class="gn-checkbox__content">
<span class="gn-checkbox__text">Notificación push</span>
<span class="gn-checkbox__description">Alerta en la app móvil si tienes la aplicación instalada.</span>
</span>
</label>
</div>
</div>
</fieldset>
</div>
El patrón form define cómo componer campos de formulario con etiquetas, hints, mensajes de error y acciones. Los estilos viven en forms.css (incluido en el bundle principal); este patrón muestra la combinación correcta de componentes.
Estructura de un campo
Cada campo se envuelve en .gn-field. El bloque textual (label, hint, error) se agrupa en .gn-field__header para aplicar el espaciado correcto:
Los controles disponibles son gn-input, gn-select, gn-textarea, gn-checkbox y gn-radio.
Espaciado interno de campos
La norma de espaciado es transversal a todos los campos del sistema:
| Espacio | Entre | Token | |---|---|---| | spacing-1 (4 px) | Label/legend → hint → error (bloque textual) | --gn-spacing-1 | | spacing-2 (8 px) | Bloque textual (.gn-field__header) → control u opciones | --gn-spacing-2 | | spacing-1 (4 px) | Label de opción → descripción de opción | --gn-spacing-1 |
El elemento .gn-field__header agrupa label (o legend), hint y error con gap: spacing-1, tanto en campos simples como en grupos (fieldset). El propio .gn-field separa el header del control u opciones con spacing-2.
Campo obligatorio
Añadir el asterisco con gn-field__required dentro de la etiqueta y el texto para lectores de pantalla con sr-only:
Estado de error
Cuando un campo no supera la validación:
- Añadir
gn-field--invalidal.gn-field— muestra la barra de error lateral roja. - Añadir el modificador de error al control:
gn-input--error,gn-select--errorogn-textarea--error. - Añadir
aria-invalid="true"al control. - Poner el mensaje de error dentro de
.gn-field__header, después del hint (si lo hay). - Vincular el error con
aria-describedbyapuntando aliddelp.gn-field__error.
Error: Mensaje de error.
La barra roja se posiciona a la izquierda del campo mediante ::before con position: absolute e inset-inline-start negativo. El contenedor padre necesita espacio libre a la izquierda para que sea visible (el layout de página ya lo provee; en previews aislados añadir padding-inline-start: 20px).
Grupos (fieldset)
Para radios o checkboxes, usar fieldset.gn-field. El bloque textual (legend + hint + error) se agrupa en .gn-field__header, igual que en un campo simple, y queda a spacing-2 de las opciones:
- Error de grupo:
gn-fieldset--invalid(nogn-field--invalid) en elfieldset. legend.gn-field__legenden lugar delabel.gn-field__label.aria-describedbyen el fieldset apuntando al hint y al error.- Los controles individuales llevan
gn-radioogn-checkbox gn-checkbox--error+aria-invalid="true".
Opciones con descripción
Cuando cada opción lleva una descripción secundaria, usar .gn-radio__content o .gn-checkbox__content como contenedor del text + description:
El gap: spacing-1 entre .gn-radio__text y .gn-radio__description lo proporciona .gn-radio__content en radio.css.
Acciones de formulario
Las acciones se agrupan en .gn-form-actions, que es un flex-row con wrap y separación estándar respecto al último campo:
Accesibilidad
- Usar siempre
labelvisible asociada porfor/id. Evitararia-labelcomo sustituto de una etiqueta visible. - El hint debe ir dentro de
.gn-field__header, antes del control, para que el lector de pantalla lo anuncie antes de que el usuario empiece a interactuar. - El error va también en
.gn-field__header(antes del control) y se vincula conaria-describedbyen el control. - En grupos (fieldset), el
legendse agrupa con el hint y el error dentro de.gn-field__header. Poneraria-describedbyen elfieldsetapuntando al hint y al error. Los controles individuales no necesitanaria-describedbypropio. - Usar
novalidateen elformpara desactivar la validación nativa del navegador y gestionar los errores de forma programática. - Añadir
role="alert"al mensaje de error solo si se inyecta dinámicamente después de una acción del usuario.
Responsive
Los campos son width: 100% por defecto. El ancho máximo de los inputs y selects lo controla el layout de la página (columna central del template). No es necesaria ninguna clase adicional para adaptar el formulario a móvil.