Boas Práticas de HTML
Um HTML sólido é a base de todo ótimo site. Estas dicas ajudam você a escrever marcação semântica, robusta e preparada para o futuro, que navegadores e leitores de tela adoram.
Definir atributos width e height explícitos nas imagens permite que o navegador reserve o espaço correto antes da imagem carregar, eliminando o Cumulative Layout Shift (CLS) — uma das Core Web Vitals do Google.
<!-- ❌ Ruim: navegador não sabe o tamanho até a imagem carregar --> <img src="hero.jpg" alt="Imagem hero"> <!-- ✅ Bom: espaço reservado imediatamente --> <img src="hero.jpg" alt="Imagem hero" width="1200" height="630" loading="lazy">
aspect-ratio: auto no CSS e o navegador calculará a proporção correta automaticamente, mesmo quando o CSS redimensiona a imagem.Um esboço de documento construído com cabeçalhos aninhados corretamente ajuda tanto os mecanismos de busca a entender sua hierarquia de conteúdo quanto os usuários de leitores de tela a navegar pela página de forma eficiente.
<h1>Título da Página (apenas um por página)</h1> <h2>Seção Principal</h2> <h3>Subseção</h3> <h3>Outra subseção</h3> <h2>Outra Seção Principal</h2>
Ao abrir links em uma nova aba com target="_blank", a página aberta pode acessar sua página via window.opener. Adicionar rel="noopener noreferrer" previne essa vulnerabilidade de segurança e também impede o envio de informações de referrer.
<!-- ❌ Vulnerável --> <a href="https://exemplo.com" target="_blank">Visitar</a> <!-- ✅ Seguro --> <a href="https://exemplo.com" target="_blank" rel="noopener noreferrer">Visitar</a>
O atributo autocomplete informa aos navegadores e gerenciadores de senhas como pré-preencher campos de formulário. Isso melhora drasticamente as taxas de conclusão de formulários no mobile.
<input type="text" autocomplete="given-name"> <input type="email" autocomplete="email"> <input type="tel" autocomplete="tel"> <input type="password" autocomplete="current-password"> <input type="text" autocomplete="postal-code">
As meta tags Open Graph controlam como sua página aparece quando compartilhada em plataformas de mídia social como Facebook, LinkedIn e Twitter/X. Sem elas, as plataformas escolhem o conteúdo aleatoriamente.
<meta property="og:title" content="Título da Página"> <meta property="og:description" content="Breve descrição"> <meta property="og:image" content="https://seudominio.com/og.jpg"> <meta property="og:url" content="https://seudominio.com/pagina"> <meta property="og:type" content="website"> <!-- Twitter/X --> <meta name="twitter:card" content="summary_large_image">
Dicas e Truques de CSS
O CSS moderno é incrivelmente poderoso. Estas dicas cobrem layout, propriedades personalizadas e padrões que economizam seu tempo e mantêm suas folhas de estilo limpas.
Pare de lutar com hacks de posicionamento. O atalho place-items do CSS Grid centraliza o conteúdo tanto horizontal quanto verticalmente em duas linhas — sem calc, sem transforms.
.centraliza-tudo { display: grid; place-items: center; } /* Funciona também para centralização de página inteira */ body { display: grid; place-items: center; min-height: 100vh; }
As propriedades personalizadas CSS (variáveis) permitem que você defina seus tokens de design uma vez e os reutilize em qualquer lugar. Elas também se atualizam em tempo real, tornando o modo escuro e a tematização triviais.
:root { --cor-primaria: #6366f1; --cor-fundo: #ffffff; --raio-md: 12px; --espacamento-md: 16px; } @media (prefers-color-scheme: dark) { :root { --cor-fundo: #0d1117; } } .btn { background: var(--cor-primaria); border-radius: var(--raio-md); padding: var(--espacamento-md); }
clamp(min, preferred, max) permite que os tamanhos de fonte escalem suavemente com a largura do viewport — sem necessidade de media queries. O valor do meio geralmente é uma unidade vw.
/* clamp(mínimo, preferido, máximo) */ h1 { font-size: clamp(1.8rem, 5vw, 3.5rem); } p { font-size: clamp(0.9rem, 2vw, 1.1rem); line-height: 1.7; max-width: 65ch; /* largura ideal de leitura */ }
Habilite rolagem suave globalmente e use scroll-margin-top nos alvos da âncora para garantir que cabeçalhos fixos não se sobreponham ao conteúdo rolado.
@media (prefers-reduced-motion: no-preference) { html { scroll-behavior: smooth; } } /* Compensação para cabeçalho fixo (ex: 70px de altura) */ [id] { scroll-margin-top: 90px; }
scroll-behavior: smooth em uma media query prefers-reduced-motion: no-preference — alguns usuários sentem enjoo com rolagem animada.O padrão auto-fill + minmax() cria um grid que ajusta automaticamente o número de colunas com base no espaço disponível — sem necessidade de media queries.
.grid-de-cards { display: grid; grid-template-columns: repeat( auto-fill, minmax(280px, 1fr) ); gap: 24px; } /* 1 col no mobile → 2 → 3 → 4 automaticamente */
Nunca faça outline: none globalmente — isso quebra a navegação por teclado. Use :focus-visible para mostrar anéis de foco apenas para usuários de teclado, mantendo o design limpo para usuários de mouse.
/* ❌ Nunca faça isso */ * { outline: none; } /* ✅ Esconde para mouse, mostra para teclado */ :focus:not(:focus-visible) { outline: none; } :focus-visible { outline: 2px solid #6366f1; outline-offset: 3px; border-radius: 4px; }
Dicas de JavaScript
Escreva JavaScript mais limpo e performático com esses padrões modernos e APIs do navegador.
Em vez de escutar o evento scroll (que dispara centenas de vezes por segundo), use a API IntersectionObserver para reagir apenas quando elementos entram ou saem do viewport.
const observer = new IntersectionObserver( (entries) => { entries.forEach(entry => { if (entry.isIntersecting) { entry.target.classList.add('visivel'); observer.unobserve(entry.target); // para de observar } }); }, { rootMargin: '0px 0px -50px 0px' } ); document.querySelectorAll('.anima-na-rolagem') .forEach(el => observer.observe(el));
Eventos como resize e input disparam muito rapidamente. O debouncing atrasa a execução até que o usuário pare de interagir, prevenindo trabalho desnecessário.
function debounce(fn, delay = 300) { let timer; return (...args) => { clearTimeout(timer); timer = setTimeout(() => fn(...args), delay); }; } // Uso window.addEventListener('resize', debounce(() => { recalcularLayout(); }, 200) );
A API moderna navigator.clipboard é baseada em promessas e muito mais limpa do que o antigo hack document.execCommand('copy').
async function copiarParaClipboard(texto) { try { await navigator.clipboard.writeText(texto); mostrarToast('Copiado!'); } catch (err) { console.error('Falha ao copiar:', err); } } // Uso document.querySelector('.btn-copiar') .addEventListener('click', () => copiarParaClipboard('Texto para copiar') );
localStorage pode lançar exceção no modo de navegação anônima ou quando o armazenamento está cheio. Sempre envolva-o em um helper try/catch.
const storage = { get(chave, fallback = null) { try { const item = localStorage.getItem(chave); return item ? JSON.parse(item) : fallback; } catch { return fallback; } }, set(chave, valor) { try { localStorage.setItem(chave, JSON.stringify(valor)); return true; } catch { return false; } } }; storage.set('tema', 'escuro'); const tema = storage.get('tema', 'claro');
Para animações orientadas por JavaScript (ex: canvas, GSAP), verifique a configuração prefers-reduced-motion do usuário e simplifique ou desabilite as animações conforme apropriado.
const movimentoReduzido = window .matchMedia('(prefers-reduced-motion: reduce)') .matches; if (!movimentoReduzido) { iniciarAnimacoes(); } else { mostrarFallbackEstatico(); }
Dicas de Performance
Sites rápidos têm melhor ranking, convertem mais e mantêm os usuários felizes. Estas dicas cobrem os maiores ganhos para performance web.
Sem font-display: swap, os navegadores escondem o texto até que a fonte personalizada carregue (FOIT). Com ela, a fonte do sistema é mostrada imediatamente, depois trocada — melhorando vastamente a Largest Contentful Paint (LCP).
<!-- Passo 1: preconnect --> <link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <!-- Passo 2: adicione &display=swap à URL --> <link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;700&display=swap" rel="stylesheet">
Imagens WebP e AVIF são significativamente menores que JPEGs e PNGs com qualidade igual. Use o elemento <picture> para servir formatos modernos com um fallback JPEG para navegadores mais antigos.
<picture> <source srcset="hero.avif" type="image/avif"> <source srcset="hero.webp" type="image/webp"> <img src="hero.jpg" alt="Imagem hero" width="1200" height="630" loading="lazy"> </picture>
Tags <script> regulares bloqueiam o parsing do HTML. defer executa o script após o parsing ser concluído (em ordem). async executa scripts assim que são baixados (ordem não garantida).
<!-- Bloqueia renderização ❌ --> <script src="app.js"></script> <!-- Executa em ordem, após o DOM ✅ (use para maioria dos scripts) --> <script src="app.js" defer></script> <!-- Executa imediatamente quando pronto ✅ (analytics, anúncios) --> <script src="analytics.js" async></script>
Limite-se a animar transform e opacity — elas executam na thread do compositor da GPU sem disparar layout ou paint. Adicione will-change apenas se você fizer profiling e ver um benefício real.
/* ❌ Dispara layout (caro) */ .ruim { transition: width 0.3s, top 0.3s; } /* ✅ Composto pela GPU (barato) */ .bom { transition: transform 0.3s, opacity 0.3s; } /* Use com moderação, apenas antes de animações complexas */ .modal-entrando { will-change: transform; } .modal-entrou { will-change: auto; } /* reseta depois */
Use <link rel="preload"> para dizer ao navegador para buscar recursos críticos mais cedo — antes que ele os descubra no CSS ou JS. Ideal para imagens hero, fontes críticas e scripts-chave.
<!-- Pré-carregar imagem hero (elemento LCP) --> <link rel="preload" as="image" href="hero.webp" fetchpriority="high"> <!-- Pré-carregar fonte crítica --> <link rel="preload" as="font" href="fonte.woff2" type="font/woff2" crossorigin>
Dicas de Acessibilidade
Acessibilidade não é uma checklist — é bom design. Estas dicas ajudam a tornar seus sites utilizáveis por todos, incluindo usuários de leitores de tela e navegação por teclado.
Botões que contêm apenas um ícone não têm texto visível para os leitores de tela anunciarem. Adicione um aria-label para fornecer um rótulo descritivo.
<!-- ❌ Leitor de tela diz "botão" --> <button><i class="fa-solid fa-times"></i></button> <!-- ✅ Leitor de tela diz "Fechar diálogo" --> <button aria-label="Fechar diálogo"> <i class="fa-solid fa-times" aria-hidden="true"></i> </button>
O atributo lang informa aos leitores de tela qual idioma usar, permite a hifenização correta no CSS e ajuda as ferramentas de tradução a identificar o idioma do conteúdo. Para idiomas RTL, adicione também dir="rtl".
<!-- Inglês --> <html lang="en"> <!-- Árabe (RTL) --> <html lang="ar" dir="rtl"> <!-- Francês --> <html lang="fr">
WCAG 2.1 AA exige uma taxa de contraste de pelo menos 4.5:1 para texto normal e 3:1 para texto grande (18px+ negrito ou 24px+ normal). Use um verificador de contraste antes de publicar.
/* ❌ #6b7280 sobre #fff = 4.48:1 (falha AA para texto pequeno) */ .texto-suave { color: #6b7280; background: #fff; } /* ✅ #4b5563 sobre #fff = 7.0:1 (passa AA e AAA) */ .texto-suave { color: #4b5563; background: #fff; }
Às vezes você precisa de texto que apenas leitores de tela possam ouvir — como um link "pular para o conteúdo" ou um rótulo para um elemento visual. A classe utilitária .sr-only o oculta visualmente enquanto o mantém acessível.
.sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0; } <!-- Exemplo de link pular --> <a href="#principal" class="sr-only">Pular para o conteúdo</a>
Quando um modal está aberto, o foco do teclado deve ser confinado dentro dele. Caso contrário, Tab focará elementos atrás da sobreposição — tornando o modal inutilizável para usuários de teclado.
function prenderFoco(modal) { const focaveis = modal.querySelectorAll( 'a, button, input, select, textarea, [tabindex]:not([tabindex="-1"])' ); const primeiro = focaveis[0]; const ultimo = focaveis[focaveis.length - 1]; modal.addEventListener('keydown', e => { if (e.key !== 'Tab') return; if (e.shiftKey ? document.activeElement === primeiro : document.activeElement === ultimo) { e.preventDefault(); (e.shiftKey ? ultimo : primeiro).focus(); } }); primeiro.focus(); }
Dicas de Design Responsivo e Mobile
O tráfego mobile é dominante. Estas dicas garantem que seus designs funcionem perfeitamente em todos os tamanhos de tela — de um celular de 320px a um monitor 4K.
Sem a meta tag viewport, navegadores mobile renderizam a ~980px de largura desktop e depois diminuem o zoom. Permita o dimensionamento do usuário — desabilitá-lo é uma grande barreira de acessibilidade e quebra o zoom do navegador.
<!-- ❌ Desabilita o zoom do usuário (violação de acessibilidade) --> <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no"> <!-- ✅ Correto: permite dimensionar até 5× --> <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=5">
As diretrizes de Interface Humana da Apple e WCAG 2.5.5 recomendam um alvo de toque mínimo de 44×44 pixels CSS. Alvos pequenos frustram os usuários e levam a toques errados. Você pode expandir a área clicável sem alterar o tamanho visual usando padding ou pseudo-elementos.
/* Expande área de toque sem mudança visual */ .btn-icone { position: relative; width: 24px; height: 24px; } .btn-icone::before { content: ''; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); min-width: 44px; min-height: 44px; }
Propriedades lógicas CSS como margin-inline-start em vez de margin-left invertem automaticamente a direção com base no modo de escrita do documento — tornando o suporte RTL quase sem esforço.
/* Físico (não inverte para RTL) */ .icone { margin-right: 8px; } /* Lógico (inverte automaticamente em RTL) */ .icone { margin-inline-end: 8px; } /* Mais exemplos de propriedades lógicas */ .card { padding-inline: 16px; /* esquerda + direita */ padding-block: 12px; /* topo + fundo */ border-start-start-radius: 12px; /* topo-esquerda em LTR */ }
100vh em navegadores mobile inclui a interface do navegador (barra de endereço), causando overflow quando a barra aparece. A nova unidade dvh (dynamic viewport height) se ajusta conforme a interface do navegador aparece ou some.
/* ❌ Causa overflow no mobile quando a interface aparece */ .hero { height: 100vh; } /* ✅ Com fallback para navegadores antigos */ .hero { height: 100vh; /* fallback */ height: 100dvh; /* altura dinâmica do viewport */ }
svh (small viewport, sempre exclui a interface) e lvh (large viewport, sempre inclui espaço da interface).Mais do ResponsiveCheckTool
Coloque Estas Dicas em Prática
Teste como suas mudanças responsivas ficam em celular, tablet e desktop — instantaneamente, de graça, sem necessidade de cadastro.
Teste Seu Site Agora