Detectar recurso do navegador, não o nome dele: o fim do if no user-agent
Troque o if que lê o user-agent por detecção de recurso em JavaScript e CSS, com o mesmo HTML para todos e um caminho que funciona quando o recurso não existe.
A loja de móveis tinha, no PHP do checkout, um if que procurava "Chrome" no user-agent para decidir entre o seletor de data nativo e um plugin antigo. Quem usava Edge recebia o nativo por acaso, porque o user-agent do Edge também contém "Chrome". Quem usava Safari recebia o plugin, que não funcionava no iPhone, e a data de entrega ficou impossível de escolher durante uma semana. A resposta curta: pergunte se o recurso existe, não qual é o navegador. Em JavaScript, teste a API ou o atributo; em CSS, use @supports; no servidor, mande o mesmo HTML para todo mundo.
Este guia troca o if da loja por detecção de recurso, passo a passo, e mostra os poucos casos em que ler o user-agent ainda faz sentido.
Por que o user-agent é uma base ruim para decisão
O user-agent é uma frase que o navegador manda em cada requisição, e ela carrega décadas de compatibilidade empilhada. Por razões históricas, o Chrome diz "Safari", o Edge diz "Chrome", e quase todos dizem "Mozilla". Procurar uma palavra nela dá falso positivo com facilidade.
Além disso, a frase muda. Os navegadores baseados em Chromium reduziram a informação do user-agent nos últimos anos, congelando partes como a versão do sistema operacional. Um código que extrai versão de dentro da frase pode passar a ler um número fixo e tomar a decisão errada sem nenhum erro aparecer.
O ponto mais importante: o nome do navegador não diz o que ele sabe fazer. A mesma marca, em versões diferentes, em celular ou desktop, com uma configuração corporativa, tem recursos diferentes. Detectar o nome é adivinhar o recurso. Detectar o recurso é perguntar direto.
O código da loja, antes
<?php
$ua = $_SERVER['HTTP_USER_AGENT'] ?? '';
$usarDataNativa = strpos($ua, 'Chrome') !== false;
?>
<?php if ($usarDataNativa): ?>
<input type="date" name="entrega" min="2026-10-20">
<?php else: ?>
<input type="text" name="entrega" class="datepicker-antigo">
<?php endif; ?>
Há três decisões erradas nesse trecho: o critério (a palavra "Chrome"), o lugar (o servidor decidindo algo que só o navegador sabe) e a falta de alternativa (se o plugin falha, não sobra nada).
Como trocar por detecção de recurso
- Escreva o HTML que funciona no pior caso, sem depender do recurso novo.
- Mande esse mesmo HTML para todos os navegadores.
- No navegador, teste o recurso com uma checagem direta.
- Se existir, use. Se não existir, deixe o HTML base funcionando ou carregue a alternativa.
- Para estilo, use
@supportsno CSS em vez de classe por navegador. - No servidor, valide os dados do mesmo jeito, venham de onde vierem.
A data de entrega, depois
O campo nativo já é a melhor base: navegador que não conhece type="date" o trata como texto. Então o HTML fica um só, com uma dica de formato que serve aos dois casos:
<label for="entrega">Data de entrega</label>
<input type="date" id="entrega" name="entrega" min="2026-10-20"
aria-describedby="entrega-dica">
<p id="entrega-dica">Formato: dia/mês/ano. Entregamos de segunda a sábado.</p>
E o JavaScript só entra se o campo nativo não existir:
function suportaCampoData() {
const input = document.createElement('input');
input.setAttribute('type', 'date');
return input.type === 'date';
}
if (!suportaCampoData()) {
import('./seletor-de-data.js').then(m => m.ligar(document.getElementById('entrega')));
}
O truque do input.type funciona porque o navegador que não conhece o tipo volta a propriedade para "text". Na prática, hoje quase todo navegador moderno passa no teste e o plugin nem é baixado. Quem não passa ainda tem um campo de texto com a dica do formato, e o PHP confere a data recebida.
Outras checagens que você vai usar
// API existe no objeto global
if ('IntersectionObserver' in window) { /* animar ao rolar */ }
// API que só existe em contexto seguro (HTTPS)
if (navigator.clipboard && window.isSecureContext) { /* botão Copiar */ }
// Compartilhamento nativo, comum em celular
if (navigator.share) { /* mostrar botão Compartilhar */ }
// Propriedade CSS consultada pelo JavaScript
if (CSS.supports('display', 'grid')) { /* layout em grade */ }
No CSS, o equivalente é @supports. A galeria de produtos da loja começa num layout simples e ganha a grade onde ela existe:
.galeria { display: flex; flex-wrap: wrap; gap: 1rem; }
@supports (display: grid) {
.galeria {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr));
}
}
Por que o servidor não deve decidir o layout
Quando o PHP manda HTML diferente conforme o user-agent, qualquer cache entre o servidor e a pessoa (CDN, proxy reverso, cache de página do próprio site) pode guardar a versão de um navegador e entregar para outro. Para evitar isso, a resposta precisaria do cabeçalho Vary: User-Agent, que fragmenta o cache em milhares de variações e derruba o ganho dele. O guia sobre deixar o site mais rápido mostra por que cache bom é cache simples.
Mandar o mesmo HTML e deixar CSS e JavaScript se adaptarem resolve os dois problemas: o cache serve todo mundo e a decisão fica com quem sabe a resposta.
Quando ler o user-agent ainda faz sentido
Para estatística e log, ele é útil: saber que 40% das visitas vêm de celular ajuda a priorizar testes. Para contornar um defeito específico de uma versão específica, quando não existe outra forma de detectar, pode ser o último recurso, com comentário dizendo qual defeito, qual versão e quando remover. Alguns navegadores baseados em Chromium também enviam client hints, cabeçalhos estruturados que substituem parte do user-agent, mas nem todos os navegadores os enviam, então não sirvem como única base de decisão. A função get_browser() do PHP, citada em muitos tutoriais, depende de um arquivo de definições externo e herda o mesmo problema: identifica nome, não recurso.
Erros comuns
- Procurar uma palavra no user-agent. "Chrome" aparece no Edge e em outros navegadores, e "Safari" aparece no Chrome; o critério pega quem não devia.
- Extrair versão da frase e comparar. Com a redução do user-agent, a versão lida pode estar congelada e a comparação passa a mentir.
- Servir HTML diferente por navegador. O cache entrega a versão errada para outra pessoa, ou o site perde o cache.
- Não ter caminho quando o recurso não existe. O botão some ou o formulário trava, em vez de funcionar de forma mais simples.
- Confiar na validação do navegador. Campo nativo ajuda quem preenche, mas o servidor precisa da mesma regra, como mostra o post sobre validação no navegador que não basta.
- Testar só no seu navegador. O defeito aparece justamente no aparelho que ninguém da equipe usa.
Checklist
- Nenhuma decisão de layout ou funcionalidade depende do texto do user-agent.
- O servidor manda o mesmo HTML para todos os navegadores.
- Recursos opcionais são testados com checagem direta antes de usar.
- Estilos novos ficam dentro de
@supports, com uma base que funciona sem eles. - Sem o recurso, a página ainda permite concluir a tarefa.
- Todo dado recebido é validado no servidor.
- Uso do user-agent, se existir, tem comentário com motivo e data para revisão.
Se você usa eventos de clique e toque, o post sobre mouse que não é o único ponteiro aplica a mesma ideia à entrada do usuário. Agora procure no seu código por HTTP_USER_AGENT ou navigator.userAgent: cada if que você encontrar olha o nome do navegador ou a função que você vai chamar?
Referência: http://www.mauricioprogramador.com.br/posts/como-identificar-navegador-com-php