JSFiddle - React, Tailwind, and code Playground

by jo0nax232

HTML

<div class="contenedor">
  <h3>Formulario dinámico</h3>
    <form action="" id="formulario">
      <div class="row-form">
        <input type="text" name="textNombre" id="textNombre" data-label="Nombre" data-guardar="true" data-tipo="text">
      </div>
      <div class="row-form">
          <input type="text" name="textApellido" id="textApellido" data-label="Apellidos" data-guardar="true" data-tipo="text">
      </div>
          <div class="row-form">
              <select name="cboCiudad" id="cboCiudad" data-label="Ciudad" data-guardar="true" data-tipo="select">
                  <option value="0">Selecione una opción</option>
                  <option value="1">Xalapa</option>
                  <option value="2">Veracruz</option>
                  <option value="3">Orizaba</option>
              </select>
          </div>
          <div class="row-form">
              <div class="input-radio">
                  <div>
                      <span class="label">Sexo</span>
                  </div>
                  <div class="radios">
                      <div>
                          <input type="radio" name="sexo" id="radioMasculino" data-label="Sexo" data-guardar="true" value="Masculino" data-tipo="radio">
                      </div>
                      <div>
                          <input type="radio" name="sexo" id="radioFemenino" data-label="Sexo" data-guardar="true" value="Femenino" data-tipo="radio">
                      </div>
                  </div>
              </div>
          </div>
          <div class="row-form">
              <button id="btnEnviar" type="submit">Enviar</button>
          </div>
      </form>
  </div>

CSS

:root{
    --border-radius: 5px;
}
*,
*::after,
*::before{
    margin: 0;
    padding: 0;
    box-sizing: border-box;
    outline: none;
}

body{
    font-size: 14px;
    font-family: Arial, Helvetica, sans-serif;
    color: #5c5c5c;
}

h3{
    font-weight: normal;
    font-size: 2em;
    margin-bottom: 0.5em;
}

label{
    font-weight: normal;
}

.contenedor{
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: flex-start;
    height: 100%;
    padding-top: 2em;
    font-size: 1.3em;
    width: 70%;
    max-width: 600px;
    margin: 0 auto;
}

.contenedor form{
    width: 100%;
    display: flex;
    flex-direction: column;
}

input[type="text"],
select{
    padding: 1em;
    background-color: transparent;
    border: 1px solid #c9c9c9;
    width: 100%;
    border-radius: var(--border-radius);
}

.row-form{
    padding: 1em;
}

.label,
label{
    display: block;
    margin-bottom: 0.5em;
}

.radios{
    display: flex;
    gap: 1em;
    text-align: center;
}

button{
    display: block;
    padding: 1em;
    width: 100%;
    background-color: #337ab7;
    border: 1px solid #2e6da4;
    color: #fff;
    border-radius: var(--border-radius);
    font-size: 0.90em;
    transition: all ease 0.2s;
    text-transform: uppercase;
    letter-spacing: 5px;
}

button:hover{
    background-color: #2c6698;
    cursor: pointer;
}

JavaScript

const d = document;
const elementos = d.querySelectorAll('[data-guardar]');
const formulario = d.querySelector('#formulario');

formulario.onsubmit = () =>{
    return validarFormulario();
}
crearLabel();

function crearLabel() {
    elementos.forEach(elemento => {
        let data = elemento.dataset;
        let label = d.createElement('label');
        let padre = elemento.parentElement;

        label.htmlFor = elemento.id;
        label.innerHTML = elemento.type == 'radio' ? elemento.value : data.label;
        padre.prepend(label);
    });
}

function validarFormulario() {
    let valido = true;
    elementos.forEach(elemento => {
        if (valido) {
            let data = elemento.dataset;
            if (data.guardar) {
                let valor = elemento.value;

                switch (data.tipo) {
                    case 'select':
                        if (valor == '0') {
                            valido = false;
                            elemento.focus();
                        }
                        break;
                    case 'radio':
                        let inputRadio = d.querySelectorAll('[type="radio"]');
                        let checked = false;
                        inputRadio.forEach(radio => {
                            if (radio.checked) {
                                checked = true;
                            }
                        });
                        if (!checked) {
                            valido = false;
                        }
                        break;
                    default:
                        if (!valor) {
                            valido = false;
                        }
                        break;
                }

                if (!valido) {
                    alert(`El campo ${data.label} no puede ir vacío`);
                    elemento.focus();
                }
            }
        }
    });
    return valido;
}