Overview
The form components provide a complete appointment booking interface with multiple input types, validation, and responsive behavior. The main form is located in seleccionar-cita.html.
The appointment form (#formulario-cita) is divided into four sections:
Tipo de Trámite - Procedure type selection
Datos del DNI - ID card information
Datos de la Cita - Appointment details
Datos de Contacto - Contact information
< form id = "formulario-cita" action = "./resumen.html" method = "get" >
<!-- Form sections go here -->
</ form >
Text Input
Used for DNI number, equipment code, and support number.
< div class = "campo-formulario" >
< label for = "numero-dni" > Número de DNI: </ label >
< input type = "text"
id = "numero-dni"
name = "numero-dni"
placeholder = "12345678X"
pattern = "[0-9]{8}[A-Za-z]"
required >
</ div >
Attributes:
type="text" - Text input field
pattern="[0-9]{8}[A-Za-z]" - Regex validation (8 digits + 1 letter)
placeholder - Example text shown when empty
required - Field must be filled
Select Dropdown
Used for procedure type, province, office, and time selection.
< div class = "campo-formulario" >
< label for = "tipo-tramite" > Seleccione el tipo de trámite: </ label >
< select id = "tipo-tramite" name = "tipo-tramite" required >
< option value = "" > -- Seleccione una opción -- </ option >
< option value = "expedicion-dni" > Expedición/Renovación DNI </ option >
< option value = "expedicion-pasaporte" > Expedición/Renovación Pasaporte </ option >
< option value = "expedicion-ambos" > Expedición DNI y Pasaporte </ option >
</ select >
</ div >
Usage:
First option has empty value to force selection
Each option has a unique value attribute
Display text can be different from value
Used for validity date and appointment date.
< div class = "campo-formulario" >
< label for = "fecha-validez" > Fecha de validez: </ label >
< input type = "date"
id = "fecha-validez"
name = "fecha-validez"
required >
</ div >
Features:
Native date picker on supported browsers
Format: YYYY-MM-DD
Can add min and max attributes for date ranges
Used for contact email.
< div class = "campo-formulario" >
< label for = "email" > Email de contacto: </ label >
< input type = "email"
id = "email"
name = "email"
placeholder = "[email protected] "
required >
</ div >
Features:
Automatic email validation
Mobile keyboards show @ and .com keys
Browser validates email format
Used for phone number.
< div class = "campo-formulario" >
< label for = "telefono" > Teléfono de contacto: </ label >
< input type = "number"
id = "telefono"
name = "telefono"
placeholder = "600123456"
required >
</ div >
For phone numbers, consider using type="tel" instead of type="number" for better mobile support and to allow formatting like spaces or dashes.
Textarea
Used for additional observations.
< div class = "campo-formulario" >
< label for = "observaciones" > Observaciones adicionales: </ label >
< textarea id = "observaciones"
name = "observaciones"
rows = "4"
placeholder = "Indique cualquier información adicional que considere relevante..." ></ textarea >
</ div >
Attributes:
rows="4" - Initial height (4 lines)
resize: vertical - CSS allows vertical resizing only
Not required (optional field)
Used for renovation reason selection.
< div class = "campo-formulario" >
< label > Motivo de renovación: </ label >
< div class = "opciones-radio" >
< label class = "label-radio" >
< input type = "radio" name = "motivo" value = "caducidad" required >
Caducidad del documento
</ label >
< label class = "label-radio" >
< input type = "radio" name = "motivo" value = "perdida" >
Pérdida o sustracción
</ label >
< label class = "label-radio" >
< input type = "radio" name = "motivo" value = "deterioro" >
Deterioro del documento
</ label >
< label class = "label-radio" >
< input type = "radio" name = "motivo" value = "otros" >
Otros motivos
</ label >
</ div >
</ div >
Key Points:
All options share same name attribute
Each has unique value
Only one option can be selected
required on first option makes at least one selection mandatory
Checkboxes
Used for terms acceptance and communication preferences.
< div class = "campo-formulario" >
< label class = "label-checkbox" >
< input type = "checkbox"
id = "acepto-terminos"
name = "acepto-terminos"
required >
Acepto los términos y condiciones y confirmo que los datos proporcionados son correctos
</ label >
</ div >
< div class = "campo-formulario" >
< label class = "label-checkbox" >
< input type = "checkbox"
id = "acepto-politica"
name = "acepto-politica" >
Deseo recibir comunicaciones sobre el estado de mi trámite
</ label >
</ div >
Differences:
First checkbox: required (must be checked)
Second checkbox: optional
Each checkbox is independent
Section 1: Procedure Type
< div class = "seccion-formulario" >
< h2 >
< svg xmlns = "http://www.w3.org/2000/svg" width = "24" height = "24" viewBox = "0 0 24 24" fill = "none"
stroke = "currentColor" stroke-width = "2" stroke-linecap = "round" stroke-linejoin = "round" >
< path stroke = "none" d = "M0 0h24v24H0z" fill = "none" />
< path d = "M14 3v4a1 1 0 0 0 1 1h4" />
< path d = "M17 21h-10a2 2 0 0 1 -2 -2v-14a2 2 0 0 1 2 -2h7l5 5v11a2 2 0 0 1 -2 2z" />
< path d = "M9 9l1 0" />
< path d = "M9 13l6 0" />
< path d = "M9 17l6 0" />
</ svg >
Tipo de Trámite
</ h2 >
< div class = "campo-formulario" >
< label for = "tipo-tramite" > Seleccione el tipo de trámite: </ label >
< select id = "tipo-tramite" name = "tipo-tramite" required >
< option value = "" > -- Seleccione una opción -- </ option >
< option value = "expedicion-dni" > Expedición/Renovación DNI </ option >
< option value = "expedicion-pasaporte" > Expedición/Renovación Pasaporte </ option >
< option value = "expedicion-ambos" > Expedición DNI y Pasaporte </ option >
</ select >
</ div >
</ div >
Two-column layout for ID fields.
< div class = "seccion-formulario" >
< h2 >
< svg > <!-- ID icon --> </ svg >
Datos del DNI
</ h2 >
< div class = "campos-fila" >
< div class = "campo-formulario" >
< label for = "numero-dni" > Número de DNI: </ label >
< input type = "text" id = "numero-dni" name = "numero-dni"
placeholder = "12345678X" pattern = "[0-9]{8}[A-Za-z]" required >
</ div >
< div class = "campo-formulario" >
< label for = "equipo-expedicion" > Equipo de expedición: </ label >
< input type = "text" id = "equipo-expedicion" name = "equipo-expedicion"
placeholder = "ABC123" required >
</ div >
</ div >
< div class = "campos-fila" >
< div class = "campo-formulario" >
< label for = "fecha-validez" > Fecha de validez: </ label >
< input type = "date" id = "fecha-validez" name = "fecha-validez" required >
</ div >
< div class = "campo-formulario" >
< label for = "numero-soporte" > Número de soporte: </ label >
< input type = "text" id = "numero-soporte" name = "numero-soporte"
placeholder = "ABC123456789" required >
</ div >
</ div >
</ div >
Section 3: Appointment Details
< div class = "seccion-formulario" >
< h2 >
< svg > <!-- Calendar icon --> </ svg >
Datos de la Cita
</ h2 >
< div class = "campos-fila" >
< div class = "campo-formulario" >
< label for = "provincia" > Provincia: </ label >
< select id = "provincia" name = "provincia" required >
< option value = "" > -- Seleccione una provincia -- </ option >
< option value = "madrid" > Madrid </ option >
< option value = "barcelona" > Barcelona </ option >
< option value = "valencia" > Valencia </ option >
<!-- More options -->
</ select >
</ div >
< div class = "campo-formulario" >
< label for = "oficina" > Oficina: </ label >
< select id = "oficina" name = "oficina" required >
< option value = "" > -- Seleccione una oficina -- </ option >
< option value = "bilbao-centro" > Bilbao - Centro </ option >
< option value = "bilbao-getxo" > Getxo </ option >
< option value = "bilbao-barakaldo" > Barakaldo </ option >
</ select >
</ div >
</ div >
<!-- Date and time fields -->
</ div >
< div class = "seccion-formulario" >
< h2 >
< svg > <!-- User icon --> </ svg >
Datos de Contacto
</ h2 >
< div class = "campos-fila" >
< div class = "campo-formulario" >
< label for = "email" > Email de contacto: </ label >
< input type = "email" id = "email" name = "email"
placeholder = "[email protected] " required >
</ div >
< div class = "campo-formulario" >
< label for = "telefono" > Teléfono de contacto: </ label >
< input type = "number" id = "telefono" name = "telefono"
placeholder = "600123456" required >
</ div >
</ div >
<!-- Textarea, radio buttons, checkboxes -->
</ div >
< div class = "botones-formulario" >
< a href = "./iniciar-solicitud.html" id = "btn-volver" >
< svg xmlns = "http://www.w3.org/2000/svg" width = "20" height = "20" viewBox = "0 0 24 24"
fill = "none" stroke = "currentColor" stroke-width = "2"
stroke-linecap = "round" stroke-linejoin = "round" >
< path stroke = "none" d = "M0 0h24v24H0z" fill = "none" />
< path d = "M15 6l-6 6l6 6" />
</ svg >
Volver
</ a >
< button type = "submit" id = "btn-continuar" >
Solicitar Cita
< svg xmlns = "http://www.w3.org/2000/svg" width = "20" height = "20" viewBox = "0 0 24 24"
fill = "none" stroke = "currentColor" stroke-width = "2"
stroke-linecap = "round" stroke-linejoin = "round" >
< path stroke = "none" d = "M0 0h24v24H0z" fill = "none" />
< path d = "M9 6l6 6l-6 6" />
</ svg >
</ button >
</ div >
CSS Styles
From css/seleccionar-cita.css:
#formulario-cita {
background-color : var ( --white );
border-radius : 10 px ;
padding : 30 px ;
margin-bottom : 30 px ;
box-shadow : 0 1 px 3 px rgba ( 0 , 0 , 0 , 0.1 );
}
.seccion-formulario {
margin-bottom : 40 px ;
text-align : left ;
}
.seccion-formulario h2 {
display : flex ;
align-items : center ;
gap : 10 px ;
color : var ( --primary-blue );
font-size : 1.3 rem ;
margin-bottom : 20 px ;
padding-bottom : 10 px ;
border-bottom : 2 px solid var ( --border-light );
}
.campo-formulario {
margin-bottom : 20 px ;
}
.campos-fila {
display : flex ;
gap : 20 px ;
flex-wrap : wrap ;
}
.campos-fila .campo-formulario {
flex : 1 ;
min-width : 200 px ;
}
.campo-formulario label {
display : block ;
margin-bottom : 8 px ;
font-weight : 600 ;
color : var ( --text-gray );
}
.campo-formulario input ,
.campo-formulario select ,
.campo-formulario textarea {
width : 100 % ;
padding : 12 px 16 px ;
border : 2 px solid var ( --border-light );
border-radius : 8 px ;
font-size : 16 px ;
transition : border-color 0.3 s ease ;
box-sizing : border-box ;
font-family : Arial , Helvetica , sans-serif ;
}
.campo-formulario textarea {
resize : vertical ;
min-height : 100 px ;
}
.campo-formulario input [ type = "checkbox" ],
.campo-formulario input [ type = "radio" ] {
width : auto ;
padding : 0 ;
margin : 0 ;
cursor : pointer ;
}
Focus States
.campo-formulario input :focus ,
.campo-formulario select :focus ,
.campo-formulario textarea :focus {
outline : none ;
border-color : var ( --primary-blue );
box-shadow : 0 0 0 3 px rgba ( 0 , 51 , 102 , 0.1 );
}
Validation States
.campo-formulario input :invalid {
border-color : #ef4444 ;
}
.campo-formulario input ::placeholder {
color : #9ca3af ;
}
Radio and Checkbox Layouts
.opciones-radio {
display : flex ;
flex-direction : column ;
gap : 10 px ;
margin-top : 10 px ;
}
.label-radio ,
.label-checkbox {
font-weight : normal ;
display : flex ;
align-items : center ;
gap : 8 px ;
}
.botones-formulario {
display : flex ;
gap : 20 px ;
justify-content : center ;
margin-top : 30 px ;
flex-wrap : wrap ;
}
.botones-formulario a ,
.botones-formulario button {
display : flex ;
align-items : center ;
gap : 8 px ;
padding : 15 px 30 px ;
border : none ;
border-radius : 8 px ;
font-size : 16 px ;
font-weight : 600 ;
cursor : pointer ;
transition : all 0.3 s ease ;
min-width : 150 px ;
justify-content : center ;
font-family : Arial , Helvetica , sans-serif ;
}
#btn-volver {
background-color : var ( --border-light );
color : var ( --text-gray );
}
#btn-volver:hover {
background-color : var ( --neutral-dark );
opacity : .9 ;
scale : .99 ;
}
#btn-continuar {
background-color : var ( --primary-blue );
color : var ( --white );
}
#btn-continuar:hover {
background-color : var ( --primary-blue-dark );
opacity : .9 ;
scale : .99 ;
}
Mobile Responsive
@media ( width <= 768 px ) {
.campos-fila {
flex-direction : column ;
}
.campos-fila .campo-formulario {
min-width : auto ;
}
.botones-formulario {
flex-direction : column ;
align-items : center ;
}
.botones-formulario a ,
.botones-formulario button {
width : 100 % ;
max-width : 300 px ;
}
#formulario-cita {
padding : 20 px ;
}
}
HTML5 Validation
Built-in validation using HTML attributes:
<!-- Required field -->
< input type = "text" required >
<!-- Pattern validation (DNI format) -->
< input type = "text" pattern = "[0-9]{8}[A-Za-z]"
title = "8 dígitos seguidos de una letra" >
<!-- Email validation -->
< input type = "email" required >
<!-- Min/Max for numbers -->
< input type = "number" min = "600000000" max = "799999999" >
<!-- Date range -->
< input type = "date" min = "2025-01-01" max = "2025-12-31" >
Custom JavaScript Validation (Example)
const form = document . getElementById ( 'formulario-cita' );
form . addEventListener ( 'submit' , function ( event ) {
// Validate DNI format
const dniInput = document . getElementById ( 'numero-dni' );
const dniPattern = / ^ [ 0-9 ] {8} [ A-Za-z ] $ / ;
if ( ! dniPattern . test ( dniInput . value )) {
event . preventDefault ();
alert ( 'Formato de DNI inválido. Use 8 dígitos y 1 letra (ej: 12345678X)' );
dniInput . focus ();
return false ;
}
// Validate phone number
const phoneInput = document . getElementById ( 'telefono' );
if ( phoneInput . value . length !== 9 ) {
event . preventDefault ();
alert ( 'El teléfono debe tener 9 dígitos' );
phoneInput . focus ();
return false ;
}
// All validation passed
return true ;
});
Dynamic Field Validation
// Real-time DNI validation
const dniInput = document . getElementById ( 'numero-dni' );
dniInput . addEventListener ( 'input' , function () {
const pattern = / ^ [ 0-9 ] {8} [ A-Za-z ] $ / ;
if ( this . value && ! pattern . test ( this . value )) {
this . setCustomValidity ( 'Formato: 8 dígitos + 1 letra' );
} else {
this . setCustomValidity ( '' );
}
});
Accessibility Features
Labels and Input Association
Required Field Indication
<!-- Option 1: Asterisk in label -->
< label for = "email" > Email de contacto: * </ label >
<!-- Option 2: ARIA required -->
< input type = "email" id = "email" aria-required = "true" required >
<!-- Option 3: Visual indicator -->
< style >
.campo-formulario label [ for ] {
position : relative ;
}
input [ required ] + label ::after {
content : ' *' ;
color : red ;
}
</ style >
< div class = "campo-formulario" >
< label for = "numero-dni" > Número de DNI: </ label >
< input type = "text"
id = "numero-dni"
name = "numero-dni"
pattern = "[0-9]{8}[A-Za-z]"
title = "8 dígitos seguidos de una letra (ej: 12345678X)"
aria-describedby = "dni-error"
required >
< span id = "dni-error" class = "error-message" role = "alert" hidden >
Formato inválido. Introduzca 8 dígitos y 1 letra
</ span >
</ div >
const input = document . getElementById ( 'numero-dni' );
const error = document . getElementById ( 'dni-error' );
input . addEventListener ( 'invalid' , function () {
error . hidden = false ;
});
input . addEventListener ( 'input' , function () {
if ( this . validity . valid ) {
error . hidden = true ;
}
});
Usage Examples
Simple Contact Form
Two-Column Layout
Custom Select Styling
< form id = "contact-form" action = "#" method = "post" >
< div class = "campo-formulario" >
< label for = "name" > Nombre: </ label >
< input type = "text" id = "name" name = "name" required >
</ div >
< div class = "campo-formulario" >
< label for = "email" > Email: </ label >
< input type = "email" id = "email" name = "email" required >
</ div >
< div class = "campo-formulario" >
< label for = "message" > Mensaje: </ label >
< textarea id = "message" name = "message" rows = "4" required ></ textarea >
</ div >
< div class = "botones-formulario" >
< button type = "submit" id = "btn-continuar" > Enviar </ button >
</ div >
</ form >
Common Issues
Problem: Clicking submit button doesn’t work.
Solutions:
Check for JavaScript errors in console
Verify all required fields are filled
Check action attribute is set
Ensure button has type="submit"
Validation Not Working
Problem: Invalid data is accepted.
Solutions:
Add required attribute to mandatory fields
Check pattern attribute is correct regex
Ensure type attribute is appropriate (email, number, etc.)
Test with novalidate attribute removed from form
Mobile Layout Broken
Problem: Two-column layout doesn’t stack on mobile.
Solution: Verify media query:
@media ( width <= 768 px ) {
.campos-fila {
flex-direction : column ;
}
}
Best Practices
Form Validation:
Use HTML5 validation attributes (required, pattern, type)
Provide clear error messages
Validate on both client and server side
Show validation errors near the field
Use appropriate input types for better mobile keyboards
Accessibility:
Always associate labels with inputs
Use semantic HTML (fieldset, legend for groups)
Provide helpful placeholder text
Ensure sufficient color contrast
Test with keyboard-only navigation
Support screen readers with ARIA attributes
User Experience:
Group related fields together
Use appropriate input types
Provide examples in placeholders
Show character limits where applicable
Save form data (localStorage) to prevent data loss
Provide clear feedback on submission
The form component is located in seleccionar-cita.html (lines 52-259). It demonstrates best practices for accessible, responsive form design.