Programação Por Equipe DevBras

Capa do artigo: Teclado do celular cobrindo o botão de enviar: por que acontece e como resolver

Teclado do celular cobrindo o botão de enviar: por que acontece e como resolver

Entenda viewport visual e de layout, tire o botão de enviar de position: fixed quando não precisa, use visualViewport quando precisa e teste com o teclado de verdade.

O formulário de agendamento cabe inteiro na tela do celular: nome, telefone, data e o botão verde "Agendar" preso no rodapé. A pessoa toca no telefone, o teclado sobe, e o botão some atrás dele. Ela rola, o botão não aparece. Fecha o teclado, o botão volta. Muita gente desiste nesse ponto achando que o formulário está quebrado.

A resposta curta: na configuração padrão dos navegadores móveis atuais, o teclado se sobrepõe à página sem redimensionar o layout, e um botão com position: fixed; bottom: 0 fica embaixo dele. A solução mais robusta é a mais simples: coloque o botão no fluxo normal, logo depois do último campo, e deixe espaço no fim da página para rolar. Se o design exige barra fixa, posicione-a com a API visualViewport.

Por que o teclado esconde o botão

O navegador móvel trabalha com duas "janelas". O viewport de layout é a área onde a página é montada, e é a ele que position: fixed se prende. O viewport visual é o pedaço que a pessoa realmente vê, que muda com zoom e com o teclado.

Quando o teclado abre, o viewport visual encolhe, mas o de layout continua do mesmo tamanho. O seu rodapé fixo segue preso ao fundo do layout, que agora está atrás do teclado. O Safari do iPhone sempre funcionou assim. O Chrome no Android passou a seguir o mesmo comportamento por padrão a partir da versão 108, e oferece uma opção para voltar a redimensionar o layout, vista mais abaixo.

Outro detalhe que engana: unidades como 100vh e as mais novas 100dvh acompanham a barra de endereço do navegador, não o teclado, na configuração padrão. Um formulário com height: 100dvh e botão no fundo continua com o botão escondido.

O caminho simples: botão no fluxo

  1. Tire o botão de enviar de position: fixed e coloque-o logo depois do último campo, dentro do form.
  2. Use um button type="submit" de verdade. Assim, a tecla de ação do próprio teclado envia o formulário, mesmo com o botão fora da tela.
  3. Ajuste a tecla de ação com enterkeyhint: "next" nos campos intermediários e "send" ou "done" no último, conforme o caso.
  4. Deixe espaço no fim da página com padding-bottom, para o último campo e o botão conseguirem rolar para cima do teclado.
  5. Respeite a área segura dos aparelhos com barra de gestos, usando env(safe-area-inset-bottom).
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">

<form action="/agendar" method="post" class="form-agendamento">
  <label for="nome">Nome</label>
  <input id="nome" name="nome" autocomplete="name" enterkeyhint="next">

  <label for="tel">Telefone</label>
  <input id="tel" name="tel" type="tel" autocomplete="tel" enterkeyhint="next">

  <label for="data">Data</label>
  <input id="data" name="data" type="date">

  <button type="submit">Agendar</button>
</form>
.form-agendamento {
  padding: 1rem 1rem calc(2rem + env(safe-area-inset-bottom));
}
.form-agendamento button {
  width: 100%;
  min-height: 48px;
  margin-top: 1.5rem;
}

O env(safe-area-inset-bottom) só devolve um valor diferente de zero quando o meta viewport tem viewport-fit=cover. Ele protege contra a barra de gestos do aparelho, não contra o teclado. São dois problemas diferentes que costumam aparecer juntos.

Repare no enterkeyhint="next". Ele muda o rótulo da tecla, mas não move o foco sozinho: avançar para o próximo campo continua sendo trabalho do navegador ou do seu script. O ganho real é que, com form e botão de envio, a pessoa consegue enviar pela tecla do teclado mesmo sem ver o botão.

Quando a barra fixa é obrigatória

Em telas como um chat ou um carrinho com total sempre visível, a barra de ação precisa acompanhar o teclado. Você tem duas ferramentas.

Acompanhar o viewport visual com JavaScript

A API visualViewport informa altura e deslocamento da área visível e dispara eventos quando eles mudam. Com ela, você calcula quanto do fundo do layout está coberto e sobe a barra na mesma medida:

const vv = window.visualViewport;

function ajustarBarra() {
  const alturaLayout = document.documentElement.clientHeight;
  const coberto = alturaLayout - (vv.height + vv.offsetTop);
  document.documentElement.style.setProperty('--coberto', Math.max(0, coberto) + 'px');
}

if (vv) {
  vv.addEventListener('resize', ajustarBarra);
  vv.addEventListener('scroll', ajustarBarra);
  ajustarBarra();
}
.barra-acao {
  position: fixed;
  left: 0;
  right: 0;
  bottom: var(--coberto, 0px);
  padding-bottom: env(safe-area-inset-bottom);
}

Trate esse código como ponto de partida e teste nos aparelhos que importam para você: a animação do teclado e a rolagem automática variam entre sistemas e versões. Também é bom dar ao conteúdo um padding-bottom igual à altura da barra, para ela não esconder o último campo.

Pedir ao navegador que redimensione o layout

O Chrome e outros navegadores baseados nele aceitam a chave interactive-widget=resizes-content no meta viewport. Com ela, o teclado encolhe o viewport de layout e o rodapé fixo sobe sozinho. O suporte não é universal, e o Safari do iPhone é o caso mais importante a conferir antes de depender disso. Use como melhoria, mantendo o caminho do botão no fluxo funcionando.

O campo focado também precisa aparecer

O navegador costuma rolar até o campo focado, mas um cabeçalho fixo no topo pode esconder exatamente esse campo. A propriedade scroll-padding-top no elemento que rola compensa a altura do cabeçalho, como mostra menu fixo que não cobre o título. Se o campo surge mas a página dá um pulo de zoom ao mesmo tempo, o culpado é outro e está em zoom ao tocar no campo.

Formulário curto também ajuda: cada campo a menos é um campo a menos atrás do teclado. Formulário que não cansa mostra como enxugar sem perder dado necessário.

Como testar de verdade

Simulador de tela alta e modo responsivo do desktop não abrem teclado virtual, então mentem. O teste precisa de um aparelho, de preferência um com tela pequena, porque é onde sobra menos espaço. Abra cada formulário, toque no último campo, digite e tente enviar sem fechar o teclado. Repita com o aparelho na horizontal, onde o teclado ocupa mais da metade da tela.

Erros comuns

  • Botão de enviar com position: fixed; bottom: 0. Com o teclado aberto, ele fica escondido e a pessoa acha que não há como enviar.
  • Formulário sem elemento form ou sem botão submit. A tecla de ação do teclado não envia nada e o único caminho é o botão escondido.
  • Usar 100vh ou 100dvh esperando que acompanhe o teclado. Na configuração padrão, essas unidades ignoram o teclado.
  • Confundir área segura com teclado. env(safe-area-inset-bottom) resolve a barra de gestos, não o teclado.
  • Depender só de interactive-widget. Em navegadores sem suporte, o botão continua escondido.
  • Testar só no modo responsivo do desktop. Sem teclado virtual, o problema não aparece até o cliente reclamar.

Checklist

  • O botão de enviar está no fluxo, depois do último campo, salvo quando há motivo forte para barra fixa.
  • Os campos estão dentro de um form com button type="submit".
  • Os campos usam enterkeyhint coerente com a ação.
  • A página tem espaço no fim para o último campo rolar acima do teclado.
  • Barras fixas acompanham o viewport visual e foram testadas em aparelho.
  • Cabeçalho fixo não esconde o campo focado.
  • O teste foi feito com teclado real, em retrato e paisagem.

Abra seu formulário mais longo num celular pequeno, toque no último campo e digite algo. O botão de enviar continua alcançável sem você fechar o teclado?

Avalie este artigo

Referência: https://developer.mozilla.org/docs/Web/CSS/env