AULA 6

📋 Formulários Básicos

Aprenda a criar formulários funcionais e acessíveis usando inputs, labels, validação básica e as melhores práticas de experiência do usuário.

⏱️ 45 minutos
🎯 Intermediário
📝 4 Exercícios

🎯 Objetivos de Aprendizagem

🏗️ 1. Estrutura Básica do Formulário

🎯 Elemento Form: O Coração da Interação

O elemento <form> é o container fundamental que agrupa todos os campos de entrada e define como os dados serão processados. Ele atua como um "envelope" que organiza as informações do usuário e estabelece a comunicação com o servidor.

Por que é importante? Sem o form, os dados ficam soltos na página. Com ele, você cria um sistema organizado de coleta e envio de informações.

📋 Formulário Mínimo - Seus Primeiros Passos

Conceito: Todo formulário precisa de três elementos essenciais: um container (form), campos de entrada (input) e rótulos (label). Vamos ver o exemplo mais simples possível:

<form action="/processar" method="post">
    <!-- Label + Input = Dupla Perfeita -->
    <label for="nome">Nome:</label>
    <input type="text" id="nome" name="nome" required>
    
    <label for="email">E-mail:</label>
    <input type="email" id="email" name="email" required>
    
    <!-- Botão para enviar os dados -->
    <button type="submit">Enviar</button>
</form>

💡 Dissecando o Código

  • action="/processar": Para onde os dados vão quando enviados
  • method="post": Como os dados são enviados (POST é seguro para dados sensíveis)
  • for="nome" + id="nome": Liga o label ao input (clique no label foca o campo)
  • name="nome": Nome do campo que o servidor receberá
  • required: Campo obrigatório (não pode ficar vazio)

🎯 Atributos Essenciais do Form - Controlando o Comportamento

Entenda cada atributo: Cada atributo do form tem um propósito específico. Aqui você aprende quando e como usar cada um deles:

<form 
    action="/submit-contact"           <!-- 🎯 Para onde enviar -->
    method="post"                      <!-- 📦 Como enviar (GET/POST) -->
    enctype="multipart/form-data"      <!-- 📎 Para arquivos -->
    autocomplete="on"                  <!-- 🤖 Preenchimento automático -->
    novalidate="false"                 <!-- ✅ Habilitar validação -->
    target="_self">                    <!-- 🪟 Onde abrir resposta -->
    
    <!-- Campo oculto: dados extras que o usuário não vê -->
    <input type="hidden" name="form_id" value="contact_form_2024">
    
    <!-- Seus campos visíveis vêm aqui -->
</form>

📚 Guia dos Atributos

  • action: URL de destino. Pode ser relativa ("/contact") ou absoluta ("https://site.com/api")
  • method="GET": Dados na URL (buscas, filtros). Limite ~2000 caracteres
  • method="POST": Dados no corpo (cadastros, senhas). Sem limite prático
  • enctype: Sempre use "multipart/form-data" quando houver upload de arquivos
  • autocomplete: "off" para dados sensíveis, "on" para facilitar preenchimento
  • novalidate: Desliga validação HTML5 (útil para testes ou validação customizada)

📝 Estrutura Semântica Completa - Organizando como um Profissional

A Arte da Organização: Um formulário bem estruturado é como uma conversa organizada. Use fieldset e legend para criar "capítulos" no seu formulário, agrupando campos relacionados. Isso melhora tanto a experiência do usuário quanto a acessibilidade.

<form action="/contato" method="post" class="contact-form">
    <!-- 📋 SEÇÃO 1: Dados Pessoais -->
    <fieldset>
        <legend>Informações Pessoais</legend>
        
        <div class="form-group">
            <label for="primeiro-nome">Primeiro Nome</label>
            <input type="text" 
                   id="primeiro-nome" 
                   name="primeiro_nome" 
                   required 
                   autocomplete="given-name"
                   placeholder="Ex: João">
        </div>
        
        <div class="form-group">
            <label for="sobrenome">Sobrenome</label>
            <input type="text" 
                   id="sobrenome" 
                   name="sobrenome" 
                   required 
                   autocomplete="family-name"
                   placeholder="Ex: Silva">
        </div>
        
        <div class="form-group">
            <label for="email-contato">E-mail</label>
            <input type="email" 
                   id="email-contato" 
                   name="email" 
                   required 
                   autocomplete="email"
                   placeholder="seuemail@exemplo.com">
        </div>
    </fieldset>
    
    <!-- 💬 SEÇÃO 2: Mensagem -->
    <fieldset>
        <legend>Mensagem</legend>
        
        <div class="form-group">
            <label for="assunto">Assunto</label>
            <select id="assunto" name="assunto" required>
                <option value="">Selecione um assunto</option>
                <option value="suporte">Suporte Técnico</option>
                <option value="vendas">Vendas</option>
                <option value="geral">Informações Gerais</option>
            </select>
        </div>
        
        <div class="form-group">
            <label for="mensagem">Sua Mensagem</label>
            <textarea id="mensagem" 
                      name="mensagem" 
                      rows="5" 
                      required 
                      placeholder="Digite sua mensagem aqui..."></textarea>
        </div>
    </fieldset>
    
    <!-- 🎬 SEÇÃO 3: Ações -->
    <div class="form-actions">
        <button type="reset">Limpar</button>
        <button type="submit">Enviar Mensagem</button>
    </div>
</form>

🎯 Elementos de Organização Explicados

  • <fieldset>: Agrupa campos relacionados (como dados pessoais ou endereço)
  • <legend>: Título do grupo (aparece na borda do fieldset)
  • .form-group: Classe para organizar label + input + mensagens
  • autocomplete: Ajuda navegadores a preencher automaticamente campos conhecidos
  • placeholder: Texto de exemplo que aparece quando o campo está vazio

⚠️ Atributos Importantes

  • action: URL para onde enviar os dados
  • method: GET (URL) ou POST (corpo da requisição)
  • enctype: Tipo de codificação (files = multipart/form-data)
  • autocomplete: Habilita/desabilita preenchimento automático

🏋️‍♂️ Exercício 1: Formulário de Cadastro

Crie um formulário de cadastro de usuário com:

🎛️ 2. Tipos de Entrada - Escolhendo a Ferramenta Certa

🎯 Por que Tipos Específicos Importam?

Cada tipo de input foi criado para um propósito específico. Usar o tipo correto oferece:

  • Validação automática: Email verifica @ automaticamente
  • Interface otimizada: Number mostra teclado numérico no mobile
  • Melhor acessibilidade: Leitores de tela entendem o contexto
  • UX aprimorada: Date abre seletor de calendário

📝 Inputs de Texto - Os Fundamentais

Textos são a base: A maioria dos dados que coletamos são textuais. Vamos dominar cada variação e quando usar cada uma:

<!-- 📝 Texto simples - O mais versátil -->
<label for="nome-usuario">Nome de Usuário</label>
<input type="text" 
       id="nome-usuario" 
       name="username" 
       maxlength="20"                     <!-- Máximo 20 caracteres -->
       pattern="[a-zA-Z0-9]+"             <!-- Apenas letras e números -->
       title="Apenas letras e números"    <!-- Tooltip de ajuda -->
       placeholder="Digite seu username"
       required>

<!-- 🔐 Senha - Segurança em primeiro lugar -->
<label for="senha">Senha</label>
<input type="password" 
       id="senha" 
       name="password" 
       minlength="8"                      <!-- Mínimo 8 caracteres -->
       pattern="(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]"
       title="Mínimo 8 caracteres, incluindo maiúscula, minúscula, número e símbolo"
       required>

<!-- 📧 E-mail - Validação inteligente -->
<label for="email-comercial">E-mail Comercial</label>
<input type="email" 
       id="email-comercial" 
       name="business_email" 
       pattern="[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}$"  <!-- Pattern extra -->
       placeholder="contato@empresa.com"
       autocomplete="work email"          <!-- Autocomplete específico -->
       required>

<!-- 🌐 URL - Para links e websites -->
<label for="website">Website da Empresa</label>
<input type="url" 
       id="website" 
       name="company_website" 
       placeholder="https://exemplo.com"
       pattern="https://.*"               <!-- Força HTTPS -->
       title="URL deve começar com https://">

<!-- 📞 Telefone - Interface otimizada -->
<label for="telefone">Telefone</label>
<input type="tel" 
       id="telefone" 
       name="phone" 
       pattern="\([0-9]{2}\) [0-9]{4,5}-[0-9]{4}"  <!-- Formato brasileiro -->
       placeholder="(11) 99999-9999"
       autocomplete="tel">

💡 Dicas Práticas para Inputs de Texto

  • type="text": Use para nomes, endereços, comentários gerais
  • type="password": Sempre para senhas (texto fica oculto com •••)
  • type="email": Validação automática + teclado @ no mobile
  • type="url": Verifica formato de URL + sugere http://
  • type="tel": Abre teclado numérico no smartphone
  • maxlength/minlength: Controla tamanho mínimo e máximo
  • pattern: Regex para validação customizada

🔢 Inputs Numéricos e Data - Precisão e Praticidade

Números e datas merecem tratamento especial: Estes tipos oferecem controles visuais (spinner, calendário) e validação automática de ranges. No mobile, abrem teclados otimizados para facilitar a entrada de dados.

<!-- 🔢 Número inteiro - Idades, quantidades -->
<label for="idade">Idade</label>
<input type="number" 
       id="idade" 
       name="age" 
       min="18"                           <!-- Valor mínimo aceito -->
       max="120"                          <!-- Valor máximo aceito -->
       step="1"                           <!-- Incremento (inteiros) -->
       placeholder="Ex: 25"
       required>

<!-- 💰 Valor monetário - Com casas decimais -->
<label for="orcamento">Orçamento (R$)</label>
<input type="number" 
       id="orcamento" 
       name="budget" 
       min="0" 
       max="1000000" 
       step="0.01"                        <!-- Permite centavos -->
       placeholder="0.00">

<!-- 🎚️ Range/Slider - Experiência visual -->
<label for="experiencia">Nível de Experiência (1-10)</label>
<input type="range" 
       id="experiencia" 
       name="experience_level" 
       min="1" 
       max="10" 
       value="5"                          <!-- Valor inicial -->
       step="1"
       oninput="this.nextElementSibling.value=this.value">
<output>5</output>                        <!-- Mostra valor atual -->

<!-- 📅 Data - Seletor nativo -->
<label for="nascimento">Data de Nascimento</label>
<input type="date" 
       id="nascimento" 
       name="birth_date" 
       min="1900-01-01"                   <!-- Data mínima -->
       max="2006-12-31"                   <!-- Data máxima -->
       autocomplete="bday"                <!-- Autocomplete para aniversário -->
       required>

<!-- ⏰ Hora - Para agendamentos -->
<label for="horario-reuniao">Horário da Reunião</label>
<input type="time" 
       id="horario-reuniao" 
       name="meeting_time" 
       min="08:00"                        <!-- Horário comercial -->
       max="18:00" 
       step="900">                        <!-- 900s = 15 minutos -->

<!-- 📅⏰ Data e hora juntas -->
<label for="evento">Data e Hora do Evento</label>
<input type="datetime-local" 
       id="evento" 
       name="event_datetime" 
       min="2024-01-01T08:00" 
       max="2025-12-31T22:00">

🎯 Vantagens dos Inputs Numéricos

  • Controles visuais: Spinner (↑↓) para ajustar valores facilmente
  • Validação automática: Verifica se está dentro do range (min/max)
  • Teclado otimizado: Mobile mostra teclado numérico
  • Step: Define o incremento (1 para inteiros, 0.01 para dinheiro)
  • Range visual: Slider para seleção intuitiva de valores
  • Seletores nativos: Calendário para datas, relógio para horas

☑️ Seleções e Escolhas - Decidindo entre Opções

Quando o usuário precisa escolher: Nem tudo é texto livre. Muitas vezes precisamos que o usuário escolha entre opções predefinidas. Cada tipo de seleção serve para situações específicas:

<!-- ☑️ Checkbox único - Sim/Não simples -->
<div class="form-group">
    <input type="checkbox" 
           id="newsletter" 
           name="subscribe_newsletter" 
           value="yes">
    <label for="newsletter">
        Quero receber newsletter com novidades
    </label>
</div>

<!-- ☑️☑️☑️ Múltiplos checkboxes - Várias escolhas -->
<fieldset>
    <legend>Interesses (selecione todos que se aplicam)</legend>
    
    <div class="checkbox-group">
        <input type="checkbox" id="tech" name="interests[]" value="tecnologia">
        <label for="tech">Tecnologia</label>
    </div>
    
    <div class="checkbox-group">
        <input type="checkbox" id="design" name="interests[]" value="design">
        <label for="design">Design</label>
    </div>
    
    <div class="checkbox-group">
        <input type="checkbox" id="marketing" name="interests[]" value="marketing">
        <label for="marketing">Marketing</label>
    </div>
</fieldset>

<!-- 🔘 Radio buttons - Uma escolha apenas -->
<fieldset>
    <legend>Tamanho da Empresa</legend>
    
    <div class="radio-group">
        <input type="radio" id="pequena" name="company_size" value="pequena" required>
        <label for="pequena">Pequena (1-10 funcionários)</label>
    </div>
    
    <div class="radio-group">
        <input type="radio" id="media" name="company_size" value="media">
        <label for="media">Média (11-100 funcionários)</label>
    </div>
    
    <div class="radio-group">
        <input type="radio" id="grande" name="company_size" value="grande">
        <label for="grande">Grande (100+ funcionários)</label>
    </div>
</fieldset>

<!-- 📋 Select dropdown - Lista suspensa -->
<label for="pais">País</label>
<select id="pais" name="country" required>
    <option value="">Selecione seu país</option>
    <optgroup label="América do Sul">          <!-- Agrupa opções -->
        <option value="BR">Brasil</option>
        <option value="AR">Argentina</option>
        <option value="CL">Chile</option>
    </optgroup>
    <optgroup label="América do Norte">
        <option value="US">Estados Unidos</option>
        <option value="CA">Canadá</option>
    </optgroup>
</select>

<!-- 📋📋 Select múltiplo - Múltiplas escolhas em lista -->
<label for="habilidades">Habilidades Técnicas</label>
<select id="habilidades" name="skills[]" multiple size="5">
    <option value="html">HTML</option>
    <option value="css">CSS</option>
    <option value="javascript">JavaScript</option>
    <option value="python">Python</option>
    <option value="react">React</option>
    <option value="node">Node.js</option>
</select>

🎯 Quando Usar Cada Tipo de Seleção

  • Checkbox único: Aceitar termos, receber newsletter, lembrar login
  • Checkboxes múltiplos: Interesses, habilidades, características (0 a N escolhas)
  • Radio buttons: Gênero, plano, método de pagamento (1 escolha obrigatória)
  • Select simples: País, estado, categoria (1 escolha, muitas opções)
  • Select múltiplo: Tags, categorias, idiomas (várias escolhas, espaço limitado)
  • name="campo[]": Use [] para múltiplas escolhas no mesmo campo

💡 Boas Práticas de Inputs

  • Use o tipo de input mais específico para cada dado
  • Forneça placeholders informativos
  • Configure autocomplete adequadamente
  • Use validação HTML5 sempre que possível

🏋️‍♂️ Exercício 2: Formulário de Pesquisa

Crie um formulário de pesquisa de satisfação com:

✅ 3. Validação Nativa - Seu Escudo Contra Dados Inválidos

🛡️ Por que Validação HTML5 é Revolucionária?

Antes do HTML5, toda validação precisava ser programada em JavaScript. Agora o navegador faz o trabalho pesado para você:

  • Instantânea: Valida enquanto o usuário digita
  • Visual: Destaca campos com erro automaticamente
  • Acessível: Funciona com leitores de tela
  • Sem JavaScript: Funciona mesmo com JS desabilitado
  • Customizável: Você pode personalizar as mensagens

🎯 Atributos de Validação - Suas Ferramentas de Controle

Cada atributo é uma regra: Combine diferentes atributos para criar validações poderosas. O navegador vai verificar automaticamente se os dados atendem todos os critérios.

<form>
    <!-- ✅ Campo obrigatório com múltiplas validações -->
    <label for="nome-completo">Nome Completo</label>
    <input type="text" 
           id="nome-completo" 
           name="full_name" 
           required                            <!-- ❗ Não pode ficar vazio -->
           minlength="3"                       <!-- 📏 Mínimo 3 caracteres -->
           maxlength="100"                     <!-- 📏 Máximo 100 caracteres -->
           pattern="[A-Za-zÀ-ÿ\s]+"          <!-- 🔤 Apenas letras e espaços -->
           title="Apenas letras e espaços são permitidos">  <!-- 💬 Tooltip de ajuda -->
    
    <!-- 📋 CPF com pattern customizado -->
    <label for="cpf">CPF</label>
    <input type="text" 
           id="cpf" 
           name="cpf" 
           pattern="\d{3}\.\d{3}\.\d{3}-\d{2}"    <!-- 🔢 Formato específico -->
           placeholder="123.456.789-00"
           title="Formato: 123.456.789-00"       <!-- 📝 Exemplo no tooltip -->
           required>
    
    <!-- 📧 Confirmação de e-mail -->
    <label for="email-principal">E-mail</label>
    <input type="email" 
           id="email-principal" 
           name="email" 
           required>
    
    <label for="confirma-email">Confirme o E-mail</label>
    <input type="email" 
           id="confirma-email" 
           name="email_confirmation" 
           required
           oninput="checkEmailMatch()">       <!-- 🔄 Verificação em tempo real -->
    
    <!-- 📎 Campo de arquivo com restrições -->
    <label for="curriculo">Currículo (PDF)</label>
    <input type="file" 
           id="curriculo" 
           name="resume" 
           accept=".pdf"                      <!-- 📋 Apenas PDFs -->
           max="5242880">                     <!-- 💾 5MB máximo -->
    
    <button type="submit">Cadastrar</button>
</form>

🎯 Atributos de Validação Explicados

  • required: Campo obrigatório - usuário deve preencher
  • minlength/maxlength: Controla tamanho mínimo e máximo do texto
  • min/max: Para números e datas - define valores limite
  • pattern: Expressão regular para formato específico
  • title: Tooltip de ajuda - explica o que é esperado
  • accept: Para files - limita tipos de arquivo (.pdf, .jpg, etc.)

🔍 Patterns Comuns - Expressões Regulares Úteis

Patterns são seus aliados: Expressões regulares podem parecer complexas, mas são extremamente poderosas. Aqui estão os patterns mais úteis para formulários brasileiros:

<!-- 📮 CEP brasileiro - Flexível -->
<label for="cep">CEP</label>
<input type="text" 
       id="cep" 
       name="postal_code" 
       pattern="\d{5}-?\d{3}"              <!-- Com ou sem hífen -->
       placeholder="12345-678"
       title="Formato: 12345-678">

<!-- 📞 Telefone brasileiro - Celular ou fixo -->
<label for="celular">Celular</label>
<input type="tel" 
       id="celular" 
       name="mobile" 
       pattern="\(\d{2}\)\s\d{4,5}-\d{4}"  <!-- 4 ou 5 dígitos no meio -->
       placeholder="(11) 99999-9999"
       title="Formato: (11) 99999-9999">

<!-- 🔢 Apenas números - Códigos, IDs -->
<label for="codigo">Código Numérico</label>
<input type="text" 
       id="codigo" 
       name="code" 
       pattern="\d+"                       <!-- Um ou mais dígitos -->
       title="Apenas números">

<!-- 🚫 Sem caracteres especiais - Usernames -->
<label for="usuario">Nome de Usuário</label>
<input type="text" 
       id="usuario" 
       name="username" 
       pattern="[a-zA-Z0-9_]+"             <!-- Letras, números, underscore -->
       title="Apenas letras, números e underscore">

<!-- 📅 Data no formato brasileiro -->
<label for="data-br">Data (DD/MM/AAAA)</label>
<input type="text" 
       id="data-br" 
       name="date_br" 
       pattern="\d{2}/\d{2}/\d{4}"        <!-- DD/MM/AAAA -->
       placeholder="31/12/2024"
       title="Formato: DD/MM/AAAA">

💡 Dicionário de Patterns Úteis

  • \d: Qualquer dígito (0-9)
  • \d{3}: Exatamente 3 dígitos
  • \d{2,5}: Entre 2 e 5 dígitos
  • \d+: Um ou mais dígitos
  • [a-z]: Letras minúsculas
  • [A-Z]: Letras maiúsculas
  • [a-zA-Z]: Qualquer letra
  • \s: Espaço em branco
  • -?: Hífen opcional (pode aparecer ou não)

📝 Mensagens Customizadas

<form>
    <label for="senha-forte">Senha Segura</label>
    <input type="password" 
           id="senha-forte" 
           name="secure_password" 
           pattern="(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}"
           title="Mínimo 8 caracteres com: maiúscula, minúscula, número e símbolo"
           required>
    
    <label for="idade-minima">Idade</label>
    <input type="number" 
           id="idade-minima" 
           name="age" 
           min="18" 
           max="100"
           title="Idade deve estar entre 18 e 100 anos"
           required>
    
    <button type="submit">Validar</button>
</form>

<script>
// Mensagens customizadas em português
document.addEventListener('DOMContentLoaded', function() {
    const inputs = document.querySelectorAll('input');
    
    inputs.forEach(input => {
        input.addEventListener('invalid', function(e) {
            const validity = e.target.validity;
            
            if (validity.valueMissing) {
                e.target.setCustomValidity('Este campo é obrigatório.');
            } else if (validity.patternMismatch) {
                e.target.setCustomValidity('Formato inválido. Verifique o exemplo.');
            } else if (validity.tooShort) {
                e.target.setCustomValidity(`Mínimo ${e.target.minLength} caracteres.`);
            } else if (validity.tooLong) {
                e.target.setCustomValidity(`Máximo ${e.target.maxLength} caracteres.`);
            } else if (validity.rangeUnderflow) {
                e.target.setCustomValidity(`Valor mínimo: ${e.target.min}`);
            } else if (validity.rangeOverflow) {
                e.target.setCustomValidity(`Valor máximo: ${e.target.max}`);
            } else {
                e.target.setCustomValidity('');
            }
        });
        
        input.addEventListener('input', function(e) {
            e.target.setCustomValidity('');
        });
    });
});
</script>

⚠️ Limitações da Validação HTML5

  • Nem todos os navegadores suportam todos os recursos
  • Validação do lado cliente pode ser contornada
  • Sempre valide novamente no servidor
  • Personalize mensagens para melhor UX

♿ 4. Formulários Acessíveis - Inclusão Digital na Prática

🌍 Por que Acessibilidade Importa?

Acessibilidade não é apenas sobre pessoas com deficiência - é sobre criar experiências melhores para todos. Um formulário acessível é:

  • Mais fácil de usar: Labels claros ajudam qualquer pessoa
  • Compatível com tecnologias assistivas: Leitores de tela, navegação por teclado
  • Legalmente exigido: Muitos países têm leis de acessibilidade digital
  • Melhor para SEO: HTML semântico é melhor indexado
  • Mais resiliente: Funciona mesmo quando CSS/JS falham

🏷️ Labels Associados - A Base de Tudo

Labels são a fundação da acessibilidade: Eles dizem aos leitores de tela e outros dispositivos o que cada campo representa. Sem labels adequados, seu formulário é praticamente impossível de usar para pessoas com deficiência visual.

<!-- ✅ Associação explícita (RECOMENDADO) -->
<label for="email-usuario">E-mail:</label>
<input type="email" id="email-usuario" name="user_email">
<!-- 🔗 A conexão for="email-usuario" + id="email-usuario" -->

<!-- ✅ Associação implícita (também funciona) -->
<label>
    Nome:
    <input type="text" name="user_name">
</label>
<!-- 🔗 Input dentro do label se conecta automaticamente -->

<!-- 🏷️ Para elementos não-label -->
<span id="descricao-senha">Senha deve ter pelo menos 8 caracteres</span>
<input type="password" 
       name="password" 
       aria-describedby="descricao-senha"    <!-- 📝 Conecta à descrição -->
       aria-labelledby="titulo-senha">       <!-- 🏷️ Conecta ao título -->

<!-- 👥 Labels para grupos de campos -->
<fieldset>
    <legend>Preferências de Contato</legend>  <!-- 🎭 Título do grupo -->
    <label><input type="radio" name="contact" value="email"> E-mail</label>
    <label><input type="radio" name="contact" value="phone"> Telefone</label>
    <label><input type="radio" name="contact" value="sms"> SMS</label>
</fieldset>

🎯 Melhores Práticas para Labels

  • Sempre use labels: Todo input deve ter um label associado
  • Prefira associação explícita: for + id é mais claro
  • Labels descritivos: "E-mail" é melhor que "Digite aqui"
  • Fieldset para grupos: Agrupa radio buttons e checkboxes relacionados
  • Legend como título: Descreve o que o grupo representa

🗣️ ARIA para Formulários - Comunicação Avançada

ARIA é a linguagem dos leitores de tela: Quando HTML semântico não é suficiente, ARIA (Accessible Rich Internet Applications) oferece atributos extras para comunicar melhor com tecnologias assistivas.

<form role="form" aria-label="Formulário de cadastro">   <!-- 🎭 Identifica o propósito -->
    <!-- 📝 Campo com descrição adicional -->
    <label for="username">Nome de Usuário</label>
    <input type="text" 
           id="username" 
           name="username" 
           aria-describedby="username-help username-error"  <!-- 📋 IDs das descrições -->
           aria-required="true"                             <!-- ❗ Obrigatório (para ARIA) -->
           aria-invalid="false">                            <!-- ✅ Estado de validação -->
    <div id="username-help" class="help-text">
        Deve ter entre 3-20 caracteres, apenas letras e números
    </div>
    <div id="username-error" class="error-message" aria-live="polite"></div>
    <!-- 📢 aria-live="polite" anuncia mudanças sem interromper -->
    
    <!-- 👥 Grupo de campos relacionados -->
    <fieldset role="group" aria-labelledby="endereco-titulo">
        <legend id="endereco-titulo">Endereço de Entrega</legend>
        
        <label for="rua">Rua</label>
        <input type="text" id="rua" name="street" aria-required="true">
        
        <label for="numero">Número</label>
        <input type="text" id="numero" name="number" aria-required="true">
        
        <label for="cep-endereco">CEP</label>
        <input type="text" id="cep-endereco" name="zipcode" 
               pattern="\d{5}-\d{3}"
               aria-describedby="cep-format">
        <div id="cep-format">Formato: 12345-678</div>
    </fieldset>
    
    <!-- 📊 Status do formulário -->
    <div id="form-status" 
         aria-live="assertive"              <!-- 🚨 Anuncia imediatamente -->
         aria-atomic="true">                <!-- 📢 Lê conteúdo completo -->
    </div>
    
    <button type="submit" aria-describedby="submit-help">
        Finalizar Cadastro
    </button>
    <div id="submit-help">
        Clique para enviar os dados ou pressione Enter
    </div>
</form>

🎯 Atributos ARIA Essenciais

  • aria-label: Rótulo quando não há label visível
  • aria-labelledby: Conecta a elemento que serve como rótulo
  • aria-describedby: Conecta a descrições adicionais
  • aria-required: Indica campo obrigatório
  • aria-invalid: "true" quando campo tem erro
  • aria-live: "polite" ou "assertive" para anúncios
  • role: Define o papel do elemento

⚠️ Indicação de Erros

<form novalidate>
    <div class="form-group">
        <label for="email-required">
            E-mail <span class="required" aria-label="obrigatório">*</span>
        </label>
        <input type="email" 
               id="email-required" 
               name="email" 
               aria-required="true"
               aria-invalid="false"
               aria-describedby="email-error">
        <div id="email-error" 
             class="error-message" 
             role="alert" 
             aria-live="assertive"
             style="display: none;">
        </div>
    </div>
    
    <div class="form-group">
        <label for="phone-optional">Telefone (opcional)</label>
        <input type="tel" 
               id="phone-optional" 
               name="phone"
               aria-describedby="phone-format">
        <div id="phone-format" class="help-text">
            Formato: (11) 99999-9999
        </div>
    </div>
    
    <button type="submit">Enviar</button>
</form>

<style>
.required {
    color: #d32f2f;
    font-weight: bold;
}

.error-message {
    color: #d32f2f;
    font-size: 0.875em;
    margin-top: 4px;
}

.help-text {
    color: #666;
    font-size: 0.875em;
    margin-top: 4px;
}

.form-group {
    margin-bottom: 1rem;
}

input:invalid {
    border-color: #d32f2f;
    box-shadow: 0 0 0 2px rgba(211, 47, 47, 0.2);
}

input:valid {
    border-color: #4caf50;
}
</style>

🏋️‍♂️ Exercício 3: Formulário de Checkout

Crie um formulário de checkout e-commerce acessível:

🎨 5. Experiência do Usuário - Design que Converte

🎯 UX em Formulários: Ciência + Arte

Um formulário bem projetado pode aumentar significativamente suas conversões. A diferença entre um usuário que abandona e um que completa o cadastro está nos detalhes da experiência:

  • Visual limpo: Reduce a ansiedade e confusão
  • Feedback imediato: Usuário sabe se está no caminho certo
  • Mobile-first: Mais de 60% dos acessos são mobile
  • Microinterações: Pequenas animações guiam e tranquilizam
  • Hierarquia clara: Usuário sabe o que fazer primeiro

📱 Layout Responsivo - Mobile é Prioridade

Mobile-first não é opção, é obrigação: Seus formulários precisam funcionar perfeitamente em qualquer dispositivo. Um layout responsivo bem feito adapta não só o tamanho, mas toda a experiência de interação.

/* 📐 Layout base do formulário */
.form-container {
    max-width: 600px;                     /* 📏 Largura máxima legível */
    margin: 0 auto;                       /* 🎯 Centraliza na tela */
    padding: 20px;                        /* 📦 Espaço interno */
}

.form-group {
    margin-bottom: 1.5rem;                /* 📏 Espaço entre campos */
}

.form-row {
    display: flex;                        /* 🔄 Layout flexível */
    gap: 1rem;                           /* 📏 Espaço entre colunas */
}

.form-col {
    flex: 1;                             /* 📐 Colunas de tamanho igual */
}

/* 🏷️ Labels e inputs - Estilo profissional */
label {
    display: block;                       /* 📐 Cada label em sua linha */
    margin-bottom: 0.5rem;               /* 📏 Espaço até o input */
    font-weight: 600;                    /* ✨ Destaque visual */
    color: #333;                         /* 🎨 Cor neutra */
}

input, select, textarea {
    width: 100%;                         /* 📐 Largura total disponível */
    padding: 12px 16px;                  /* 📦 Espaço interno confortável */
    border: 2px solid #e1e5e9;          /* 🖼️ Borda sutil */
    border-radius: 8px;                  /* 🎨 Cantos arredondados */
    font-size: 16px;                     /* 📱 Evita zoom no iOS */
    transition: border-color 0.3s ease;  /* ✨ Animação suave */
}

input:focus, select:focus, textarea:focus {
    outline: none;                       /* 🚫 Remove outline padrão */
    border-color: #007bff;               /* 🎨 Azul de foco */
    box-shadow: 0 0 0 3px rgba(0, 123, 255, 0.1); /* ✨ Halo de foco */
}

/* 🎨 Estados de validação - Feedback visual */
input:valid {
    border-color: #28a745;               /* 🟢 Verde para válido */
}

input:invalid:not(:focus):not(:placeholder-shown) {
    border-color: #dc3545;               /* 🔴 Vermelho para inválido */
}

/* 📱 Responsive - Adaptação para mobile */
@media (max-width: 768px) {
    .form-row {
        flex-direction: column;           /* 📐 Empilha em mobile */
        gap: 0;                          /* 📏 Remove gap desnecessário */
    }
    
    input, select, textarea {
        font-size: 16px;                 /* 📱 Previne zoom automático no iOS */
    }
}

/* 🎭 Fieldsets - Agrupamento visual */
fieldset {
    border: 2px solid #e1e5e9;          /* 🖼️ Borda do grupo */
    border-radius: 8px;                  /* 🎨 Cantos arredondados */
    padding: 20px;                       /* 📦 Espaço interno */
    margin-bottom: 2rem;                 /* 📏 Espaço entre grupos */
}

legend {
    padding: 0 10px;                     /* 📦 Espaço nas laterais */
    font-weight: bold;                   /* ✨ Destaque no título */
    color: #007bff;                      /* 🎨 Cor de destaque */
}

🎯 Princípios de Layout Responsivo

  • Mobile-first: Comece pelo menor dispositivo
  • font-size: 16px: Evita zoom automático no iOS
  • Touch targets: Mínimo 44px de altura para dedos
  • Flexbox: Para layouts que se adaptam automaticamente
  • Media queries: Breakpoints em 768px, 1024px
  • Espaçamento consistente: Use múltiplos de 8px ou 4px

🔘 Inputs Customizados

<!-- Radio buttons customizados -->
<fieldset class="radio-fieldset">
    <legend>Escolha um plano</legend>
    
    <label class="radio-card">
        <input type="radio" name="plan" value="basic">
        <div class="radio-card-content">
            <h4>Plano Básico</h4>
            <p>Ideal para iniciantes</p>
            <strong>R$ 19/mês</strong>
        </div>
    </label>
    
    <label class="radio-card">
        <input type="radio" name="plan" value="premium">
        <div class="radio-card-content">
            <h4>Plano Premium</h4>
            <p>Para usuários avançados</p>
            <strong>R$ 49/mês</strong>
        </div>
    </label>
</fieldset>

<!-- Checkbox customizado -->
<label class="checkbox-custom">
    <input type="checkbox" name="terms">
    <span class="checkmark"></span>
    Li e aceito os <a href="/termos">termos de uso</a>
</label>

<!-- Upload de arquivo aprimorado -->
<div class="file-upload">
    <label for="file-input" class="file-label">
        <span class="file-icon">📁</span>
        <span class="file-text">Clique para selecionar arquivo</span>
        <span class="file-subtext">PDF, máximo 5MB</span>
    </label>
    <input type="file" 
           id="file-input" 
           name="document" 
           accept=".pdf"
           style="display: none;">
    <div class="file-status"></div>
</div>

🎨 CSS para Componentes

/* Radio cards */
.radio-card {
    display: block;
    position: relative;
    padding: 20px;
    border: 2px solid #e1e5e9;
    border-radius: 12px;
    margin-bottom: 1rem;
    cursor: pointer;
    transition: all 0.3s ease;
}

.radio-card:hover {
    border-color: #007bff;
    transform: translateY(-2px);
    box-shadow: 0 4px 12px rgba(0, 123, 255, 0.15);
}

.radio-card input[type="radio"] {
    position: absolute;
    opacity: 0;
    width: 0;
    height: 0;
}

.radio-card input[type="radio"]:checked + .radio-card-content {
    border-left: 4px solid #007bff;
    padding-left: 16px;
}

.radio-card input[type="radio"]:checked ~ .radio-card {
    border-color: #007bff;
    background-color: #f8f9ff;
}

/* Checkbox customizado */
.checkbox-custom {
    display: flex;
    align-items: center;
    cursor: pointer;
    font-size: 14px;
    line-height: 1.5;
}

.checkbox-custom input[type="checkbox"] {
    opacity: 0;
    position: absolute;
    width: 0;
    height: 0;
}

.checkmark {
    width: 20px;
    height: 20px;
    border: 2px solid #e1e5e9;
    border-radius: 4px;
    margin-right: 12px;
    display: flex;
    align-items: center;
    justify-content: center;
    transition: all 0.3s ease;
}

.checkbox-custom input[type="checkbox"]:checked + .checkmark {
    background-color: #007bff;
    border-color: #007bff;
}

.checkbox-custom input[type="checkbox"]:checked + .checkmark::after {
    content: "✓";
    color: white;
    font-weight: bold;
    font-size: 12px;
}

/* Upload de arquivo */
.file-upload {
    position: relative;
}

.file-label {
    display: flex;
    flex-direction: column;
    align-items: center;
    padding: 40px 20px;
    border: 2px dashed #e1e5e9;
    border-radius: 12px;
    cursor: pointer;
    transition: all 0.3s ease;
    text-align: center;
}

.file-label:hover {
    border-color: #007bff;
    background-color: #f8f9ff;
}

.file-icon {
    font-size: 48px;
    margin-bottom: 10px;
}

.file-text {
    font-weight: 600;
    color: #333;
    margin-bottom: 4px;
}

.file-subtext {
    font-size: 14px;
    color: #666;
}

/* Botões */
.btn {
    padding: 12px 24px;
    border: none;
    border-radius: 8px;
    font-size: 16px;
    font-weight: 600;
    cursor: pointer;
    transition: all 0.3s ease;
    text-decoration: none;
    display: inline-block;
    text-align: center;
}

.btn-primary {
    background-color: #007bff;
    color: white;
}

.btn-primary:hover {
    background-color: #0056b3;
    transform: translateY(-1px);
    box-shadow: 0 4px 8px rgba(0, 123, 255, 0.3);
}

.btn-secondary {
    background-color: #6c757d;
    color: white;
}

.btn-outline {
    background-color: transparent;
    border: 2px solid #007bff;
    color: #007bff;
}

.btn-outline:hover {
    background-color: #007bff;
    color: white;
}

🏋️‍♂️ Exercício 4: Formulário de Inscrição Completo

Crie um formulário de inscrição em evento com design completo:

📋 Resumo da Aula

🎓 O que Você Dominou Hoje

Parabéns! Você completou uma jornada completa pelos formulários HTML. Agora você tem conhecimento sólido para criar formulários profissionais, acessíveis e com excelente experiência do usuário.

🏗️ Estrutura de Formulário

Form, fieldset, legend e organização semântica. Você aprendeu a criar formulários bem estruturados que são fáceis de usar e manter.

  • Elementos form e seus atributos essenciais
  • Organização com fieldset e legend
  • Boas práticas de nomenclatura

🎛️ Tipos de Input

Domínio completo de todos os tipos: text, email, number, date, radio, checkbox, select e file. Cada tipo para sua situação específica.

  • Inputs de texto e suas variações
  • Campos numéricos e de data/hora
  • Elementos de seleção e escolha

✅ Validação HTML5

Validação nativa poderosa com required, pattern, min/max e mensagens customizadas. Sem necessidade de JavaScript básico.

  • Atributos de validação essenciais
  • Patterns de expressão regular úteis
  • Mensagens de erro personalizadas

♿ Acessibilidade

Formulários inclusivos com labels corretos, ARIA, indicação de erros e navegação por teclado. Experiência para todos os usuários.

  • Labels e associações adequadas
  • Atributos ARIA para contexto
  • Indicação acessível de erros

✅ Checklist de Formulários Profissionais

🚀 Próximos Passos

Agora que você domina formulários básicos, está pronto para:

  • Formulários Avançados: Multi-step, validação JavaScript, upload de arquivos
  • Integração Backend: Envio de dados para servidor
  • Frameworks: React Hook Form, Formik, VeeValidate
  • APIs: Fetch API, AJAX, FormData