Formulários e entrada

Validação instantânea de formulário

Formulário de cadastro com estado em tempo real, mensagens de erro e feedback de envio.

HTMLCSSJavaScriptFormulário
É possível editar, executar e salvar este exemplo após ativar JavaScript. O código-fonte fixo é o seguinte:
<!doctype html>
<html lang="pt-BR" dir="ltr">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Validação instantânea de formulário</title>
  <style>
    * { box-sizing: border-box; }
    body { min-height: 100vh; margin: 0; display: grid; place-items: center; padding: 24px; color: #23252b; font-family: "Avenir Next", "Microsoft YaHei", sans-serif; background: #f3e7da; }
    .shell { width: min(920px, 100%); display: grid; grid-template-columns: .86fr 1.14fr; overflow: hidden; border: 1px solid #d6c7b8; border-radius: 28px; background: #fffdf9; box-shadow: 0 32px 80px rgba(73,52,34,.14); }
    .aside { position: relative; min-height: 620px; display: flex; flex-direction: column; justify-content: space-between; overflow: hidden; padding: clamp(28px, 5vw, 48px); color: #fff; background: #28483f; }
    .aside::before { content: ""; position: absolute; width: 330px; height: 330px; top: 110px; left: -120px; border: 70px solid rgba(246,180,100,.22); border-radius: 50%; }
    .logo, .quote { position: relative; z-index: 1; }
    .logo { font: 800 18px/1 Georgia, serif; }
    .logo i { color: #f6b464; font-style: normal; }
    .quote strong { display: block; font: 500 clamp(30px, 5vw, 46px)/1.1 Georgia, "STSong", serif; }
    .quote p { color: #b9c9c4; line-height: 1.7; }
    .form-wrap { padding: clamp(28px, 6vw, 58px); }
    .eyebrow { color: #c76343; font-size: 11px; font-weight: 900; letter-spacing: .18em; }
    h1 { margin: 9px 0 8px; font: 700 clamp(34px, 6vw, 52px)/1 Georgia, "STSong", serif; }
    .intro { margin: 0 0 30px; color: #7b7d82; font-size: 14px; }
    .field { margin-bottom: 18px; }
    label { display: block; margin-bottom: 7px; color: #4a4c52; font-size: 12px; font-weight: 800; }
    input[type="text"], input[type="email"], input[type="password"] { width: 100%; padding: 13px 14px; border: 1px solid #d8d7d2; border-radius: 11px; color: #24262b; font: inherit; background: #fff; transition: border-color .18s, box-shadow .18s; }
    input:focus { border-color: #3c7566; outline: 0; box-shadow: 0 0 0 3px rgba(60,117,102,.12); }
    .field.is-invalid input { border-color: #c44f3f; box-shadow: 0 0 0 3px rgba(196,79,63,.1); }
    .field.is-valid input { border-color: #3c7566; }
    .error { min-height: 17px; margin: 5px 2px 0; color: #c44f3f; font-size: 11px; }
    .password-meta { display: flex; gap: 5px; margin-top: 8px; }
    .bar { flex: 1; height: 3px; border-radius: 3px; background: #e4e1db; }
    .bar.active { background: #3c7566; }
    .terms { display: flex; align-items: flex-start; gap: 9px; margin: 4px 0 22px; color: #6c6e73; font-size: 12px; line-height: 1.5; }
    .terms input { width: 17px; height: 17px; margin: 1px 0 0; accent-color: #3c7566; }
    .submit { width: 100%; padding: 14px 18px; border: 0; border-radius: 11px; color: #fff; font: inherit; font-weight: 850; background: #c76343; box-shadow: 0 7px 0 #8e402c; cursor: pointer; }
    .submit:active { transform: translateY(4px); box-shadow: 0 3px 0 #8e402c; }
    .submit:focus-visible { outline: 3px solid #28483f; outline-offset: 3px; }
    .result { min-height: 20px; margin: 18px 0 0; color: #28705b; font-size: 13px; font-weight: 800; text-align: center; }
    @media (max-width: 720px) { .shell { grid-template-columns: 1fr; } .aside { min-height: 240px; } .quote p { display: none; } }
    @media (prefers-reduced-motion: reduce) { input { transition: none !important; } }
  </style>
</head>
<body>
  <main class="shell">
    <aside class="aside"><div class="logo">Field<i>Note</i></div><div class="quote"><strong>Organize sua inspiração em um plano alcançável.</strong><p>Um espaço tranquilo para registrar, organizar e compartilhar seu próximo passo.</p></div></aside>
    <section class="form-wrap"><span class="eyebrow">CREATE ACCOUNT</span><h1>Criar nova conta</h1><p class="intro">Todas as validações são concluídas nesta página, e nenhum dado será enviado.</p>
      <form id="signup" novalidate>
        <div class="field"><label for="name">Apelido</label><input id="name" name="name" type="text" autocomplete="name" maxlength="24" aria-describedby="nameError"><p id="nameError" class="error"></p></div>
        <div class="field"><label for="email">E-mail</label><input id="email" name="email" type="email" autocomplete="email" aria-describedby="emailError"><p id="emailError" class="error"></p></div>
        <div class="field"><label for="password">Senha</label><input id="password" name="password" type="password" autocomplete="new-password" aria-describedby="passwordError"><div class="password-meta" aria-hidden="true"><i class="bar"></i><i class="bar"></i><i class="bar"></i></div><p id="passwordError" class="error"></p></div>
        <label class="terms"><input id="terms" type="checkbox"><span>Li e concordo com os termos de exemplo e entendo que este é apenas um formulário de demonstração local.</span></label>
        <button class="submit" type="submit">Cadastro concluído</button><p id="result" class="result" role="status" aria-live="polite"></p>
      </form>
    </section>
  </main>
  <script>
    const form = document.querySelector('#signup');
    const fields = {
      name: { input: document.querySelector('#name'), check: value => value.trim().length >= 2 ? '' : 'O apelido precisa ter pelo menos 2 caracteres.' },
      email: { input: document.querySelector('#email'), check: value => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value) ? '' : 'Digite um endereço de e-mail válido.' },
      password: { input: document.querySelector('#password'), check: value => value.length >= 8 && /[A-Za-z]/.test(value) && /\d/.test(value) ? '' : 'A senha deve ter pelo menos 8 caracteres e conter letras e números.' }
    };
    function validateField(key) {
      const field = fields[key];
      const message = field.check(field.input.value);
      const wrapper = field.input.closest('.field');
      wrapper.classList.toggle('is-invalid', Boolean(message));
      wrapper.classList.toggle('is-valid', !message);
      field.input.setAttribute('aria-invalid', String(Boolean(message)));
      document.querySelector(`#${key}Error`).textContent = message;
      return !message;
    }
    Object.entries(fields).forEach(([key, field]) => {
      field.input.addEventListener('blur', () => validateField(key));
      field.input.addEventListener('input', () => { if (field.input.closest('.field').classList.contains('is-invalid')) validateField(key); if (key === 'password') updateStrength(); });
    });
    function updateStrength() {
      const value = fields.password.input.value;
      const score = [value.length >= 8, /[A-Za-z]/.test(value) && /\d/.test(value), /[^A-Za-z0-9]/.test(value)].filter(Boolean).length;
      document.querySelectorAll('.bar').forEach((bar, index) => bar.classList.toggle('active', index < score));
    }
    form.addEventListener('submit', event => {
      event.preventDefault();
      const valid = Object.keys(fields).map(validateField).every(Boolean);
      const result = document.querySelector('#result');
      if (!document.querySelector('#terms').checked) { result.textContent = 'Primeiro marque para concordar com os termos de exemplo.'; return; }
      result.textContent = valid ? 'Verificação aprovada! Este exemplo não criará uma conta de verdade.' : 'Verifique os campos marcados em vermelho.';
      if (!valid) document.querySelector('[aria-invalid="true"]')?.focus();
    });
  </script>
</body>
</html>
Feedback da páginaEncontrou um problema ou tem uma sugestão de melhoria?
Recursos de feedback precisam ser usados na versão online

Envie somente após ativar JavaScript na versão online; a funcionalidade local da ferramenta ou do exemplo não é afetada.

Ir para o feedback na versão online