Botão de compartilhar com navigator.share: caia na cópia em navegadores sem suporte
Botão de compartilhar com navigator.share: HTTPS e gesto do usuário, fallback de cópia sem suporte e compartilhamento de arquivos.
Pedi “coloca aí um botão de compartilhar”, peguei o botão pronto e cliquei no meu próprio PC. Não aconteceu nada.
Nenhum erro. Nem a sensação de que o clique tinha registrado. Achei que era bug e fiquei quase uma hora olhando os logs até cair a ficha: no meu Chrome de desktop, aquele recurso simplesmente não existia. Quando abri no celular, a folha de compartilhamento do sistema apareceu certinha.
Esse é o lado mais traiçoeiro da Web Share API. Nos aparelhos onde funciona, é uma mão na roda; nos aparelhos onde não funciona, vira “nada”. E como não dá nenhuma reação, é difícil perceber que está quebrado. Hoje vou pegar esse botão do estado “morto em metade dos aparelhos” e deixá-lo reagindo a algo em todos eles.
Pontos principais
- O
navigator.share()abre a folha de compartilhamento padrão do sistema. É forte no celular, mas é normal haver ambientes sem suporte, como o Chrome de desktop. - A chamada tem duas restrições: exige HTTPS (ou localhost) e só funciona logo após um gesto do usuário, como clique ou toque.
- Quando não há suporte ou a chamada falha, o padrão é cair, no mesmo botão, para a cópia na área de transferência. Nunca esconda o botão.
- Para compartilhar arquivos, confirme antes com
navigator.canShare({ files }). Passar direto faz a chamada estourar uma exceção. - O
AbortError, que acontece quando o usuário só fecha a folha, não é erro. Mostrar um toast vermelho aí só gera confusão.
O que o navigator.share faz, afinal
Quando você “compartilha” algo no celular, sobe de baixo aquela folha com WhatsApp, e-mail, notas, Slack, tudo enfileirado. Chamar isso direto de uma página web é o trabalho da Web Share API, e o centro de tudo é uma única função: navigator.share().
A grande vantagem é que você não precisa montar a lista de destinos de compartilhamento.
No jeito antigo, a gente colocava ícone por ícone: X, Facebook, WhatsApp… E, com isso, o app que o usuário realmente queria usar (o bloco de notas pessoal, o Slack da empresa) quase sempre ficava de fora. Com o navigator.share(), os apps instalados no aparelho daquela pessoa já viram as opções. Você terceiriza para o sistema operacional o trabalho de enfileirar os ícones.
Os dados que você passa são simples: basta entregar um objeto neste formato.
navigator.share({
title: "Título do artigo",
text: "Uma frase de apresentação",
url: "https://example.com/article",
});
title, text e url são a base. Dá para passar só a url, ou só o text. Como cada app exibe o conteúdo do seu jeito, é mais tranquilo encarar isto aqui como “só entregar a matéria-prima”. Os argumentos detalhados estão descritos com precisão, como fonte primária, em Navigator.share() na MDN.
Saiba antes onde funciona e onde não funciona
O mais importante é se conformar com uma ideia: a Web Share API não é um recurso que roda em qualquer lugar. Como eu mesmo descobri na marra, no Chrome de desktop não há suporte (no momento em que escrevo). No Safari e nos ambientes mobile, em geral, dá para usar. Por isso, a regra de ouro é checar “se existe” antes de cada chamada.
A verificação é só isto: olhar se navigator.share existe como função.
if (typeof navigator.share === "function") {
// Ambiente em que dá para abrir a folha de compartilhamento
}
Além disso, a chamada tem duas restrições. Sem conhecê-las, você cria aqueles acidentes do tipo “funcionava no local, mas em produção não funciona” ou “o botão aparece, mas clicar gera exceção”. A própria MDN deixa isso explícito nas condições de uso da Web Share API.
| Restrição | O que é | Acidente comum |
|---|---|---|
| Contexto seguro | Só funciona em HTTPS ou em localhost | Não reage em ambiente de teste http:// ou IP direto |
| Gesto do usuário obrigatório | Bloqueado se não for logo após clique/toque | Tentar abrir o compartilhamento sozinho junto com a tela e falhar |
| Validade dos dados | O formato da url e o suporte a arquivos precisam atender às condições | URL inválida ou arquivo não suportado geram exceção |
| Permissions Policy | Dentro de iframe, só funciona se o pai liberar web-share | Em ambiente embutido, deixa de funcionar em silêncio |
Com window.isSecureContext, você confirma com true / false se a página atual está em contexto seguro. Dá para usar isso até na separação “no desenvolvimento, validar em localhost; só na URL de produção, cair para a UI de cópia”.
Pronto para copiar e colar: a versão completa de compartilhar e, se falhar, copiar
Aqui está o coração da coisa. Vou colocar a estrutura mínima que roda como está em HTML puro, em Astro, em site estático ou em PWA. A lógica tem três camadas.
- Se
navigator.shareexistir, abra com ele a folha de compartilhamento. - Se o usuário cancelar (
AbortError), volte ao estado anterior como se nada tivesse acontecido. - Se não houver suporte ou se falhar, copie para a área de transferência a partir do mesmo botão.
Primeiro, o botão e um pequeno texto que comunica o estado. Colocar aria-live faz o leitor de tela anunciar a mudança de estado.
<button data-share type="button">Compartilhar este artigo</button>
<p data-share-status aria-live="polite"></p>
Agora o script. Deixei os comentários para você conseguir acompanhar o fluxo só de lê-los.
// Matéria-prima do compartilhamento. Troque title / text / url por página
const payload = {
title: document.title,
text: "Notas de implementação de um botão de compartilhar com navigator.share.",
url: window.location.href,
};
const button = document.querySelector("[data-share]");
const statusEl = document.querySelector("[data-share-status]");
// Função mínima que só reescreve o texto de estado
function setStatus(message) {
if (statusEl) statusEl.textContent = message;
}
// Um único texto para entregar ao app de destino (usado no fallback de cópia)
function buildText(data) {
return [data.title, data.text, data.url].filter(Boolean).join("\n\n");
}
// Fallback: copiar para a área de transferência. Se nem isso der, cópia manual
async function copyFallback(data) {
const text = buildText(data);
if (navigator.clipboard && window.isSecureContext) {
await navigator.clipboard.writeText(text);
setStatus("Link copiado. Cole no chat ou nas redes sociais.");
return;
}
window.prompt("Copie este texto", text);
setStatus("Copie o texto exibido e compartilhe.");
}
button?.addEventListener("click", async () => {
// Normalizar a url para URL absoluta evita que ela se perca no destino
const data = { ...payload, url: new URL(payload.url, location.href).href };
// 1. Em ambiente compatível, abra a folha de compartilhamento
if (typeof navigator.share === "function") {
try {
await navigator.share(data);
setStatus("Folha de compartilhamento aberta.");
return;
} catch (error) {
// 2. O usuário só fechou. Não é falha, então volte em silêncio
if (error.name === "AbortError") {
setStatus("");
return;
}
// As demais exceções seguem para o fallback de cópia
console.warn("navigator.share falhou. Caindo para a cópia.", error);
}
}
// 3. Sem suporte ou falha -> copiar
try {
await copyFallback(data);
} catch (error) {
console.error("A cópia também falhou.", error);
setStatus("Não foi possível copiar. Copie a URL da barra de endereços.");
}
});
Decore só um ponto: mesmo em ambiente sem navigator.share, o mesmo botão sobrevive como “copiar”. Se você apaga o botão num condicional, o usuário conclui “esse recurso não existe” e nunca mais vai procurá-lo. Mantendo-o, você absorve as diferenças entre navegadores e ainda garante que quem clicou seja sempre empurrado para a próxima ação.
Ao migrar para React ou Vue, o conteúdo desse handler de click continua valendo na íntegra. Basta trocar o statusEl.textContent por setState. Para quem quer fechar até as permissões e os testes do lado da área de transferência, no artigo sobre a implementação da Clipboard API reuni o comportamento em caso de falha de cópia e a verificação com Playwright.
Para compartilhar arquivos, chame canShare antes
Além de URL e texto, há cenários em que você quer compartilhar imagens ou PDFs. Uma figura gerada, um PDF de recibo, uma captura de tela. Esses entram num array files, com objetos File, e são passados assim.
Só que o suporte a compartilhamento de arquivos é mais restrito que o de texto. Por isso, chamar navigator.share({ files }) de cara faz a coisa estourar uma exceção em ambiente sem suporte. O seguro é perguntar antes, com navigator.canShare(): “essa matéria-prima dá para compartilhar?”.
async function shareFile(file) {
const data = { files: [file], title: "Documento", text: "Compartilhando um PDF" };
// Confirme antes com canShare se este arquivo pode ser compartilhado
if (navigator.canShare && navigator.canShare(data)) {
try {
await navigator.share(data);
return "shared";
} catch (error) {
if (error.name === "AbortError") return "cancelled";
console.warn("Compartilhamento de arquivo falhou. Trocando para URL.", error);
}
}
// Sem suporte ou falha -> caia para compartilhar a URL de download ou copiar
return "fallback";
}
O canShare é uma função leve que só devolve true / false e, por si só, não abre a folha. É exclusivo para verificação. Quando não há suporte, em vez de forçar a entrega do arquivo, compartilhar uma URL de download já enviada reduz bastante a taxa de falha.
Em PWA há tanto “quem compartilha” quanto “quem recebe”
Aqui está a parte mais interessante da Web Share API.
O navigator.share() era sobre compartilhar para fora, a partir do seu próprio site. Por outro lado, o PWA também tem um mecanismo para ser o lado que recebe o compartilhamento (o alvo de compartilhamento) de outros apps. É aquilo de tentar compartilhar uma foto no celular e o seu PWA aparecer na lista de destinos.
O mecanismo é só escrever share_target no manifest.json do PWA. O sistema reconhece que “este PWA pode receber compartilhamentos” e o inclui entre as opções da folha.
{
"share_target": {
"action": "/share-receiver",
"method": "GET",
"params": {
"title": "title",
"text": "text",
"url": "url"
}
}
}
Escrevendo assim, os dados compartilhados por outros apps chegam com query, no formato /share-receiver?title=...&text=...&url=.... Daí é só a página receptora ler isso e salvar ou exibir. Como reuni o passo a passo da instalação do PWA e do desenho do service worker no artigo sobre transformar em PWA, vale a leitura para quem quer construir até o alvo de compartilhamento.
Há mais um motivo para o botão de compartilhar fazer diferença em PWA. Ao iniciar pela tela inicial, a barra de endereços e o menu de compartilhamento do navegador somem, então o botão de compartilhar dentro da página vira a única rota para levar o conteúdo para fora. Comparado a ver pelo navegador, a importância do botão sobe.
Três falhas que eu mesmo cometi
Vou ser honesto. Meu primeiro botão de compartilhar era cheio de furos.
A primeira foi o caso do desktop sem reação, lá do começo. Eu tinha matado a verificação de existência do navigator.share e chamava direto. Como testava no celular, não percebi, e acabei publicando em produção um “botão que não faz nada quando você clica” no PC sem suporte. Agora, com a verificação de existência e o fallback de cópia, qualquer aparelho sempre reage a algo.
A segunda foi mostrar o AbortError num toast vermelho. O usuário só fechava a folha de compartilhamento de leve e, toda vez, aparecia “falha ao compartilhar”. Fechar não é falhar. Quando mudei para engolir só o AbortError e não mostrar nada, aquele aviso estranho sumiu.
A terceira foi tentar abrir o compartilhamento junto com a exibição da página. Fiquei ganancioso (“quero que compartilhem depois de ler”) e chamei navigator.share() no onload. Sem gesto do usuário, veio o bloqueio com NotAllowedError. A Web Share API só funciona logo após clique ou toque. É óbvio, mas a tentação de abrir automaticamente é discretamente forte. Hoje eu chamo sempre a partir do click do botão.
Perguntas frequentes
P. O navigator.share não funciona no desktop. É bug?
R. Não é bug, é especificação. No momento em que escrevo, o Chrome de desktop e outros não têm suporte, e o próprio navigator.share não existe. Faça o desvio com typeof navigator.share === "function" e, sem suporte, caia para a cópia.
P. Funciona no local, mas em produção não.
R. Costuma ser por causa da restrição de contexto seguro (HTTPS ou localhost). Em staging http:// ou com IP direto, não funciona. Você confirma com window.isSecureContext.
P. Dá para medir quem compartilhou em qual app? R. Não. A Web Share API não devolve ao site o app de destino. Em troca, registre separadamente quatro tipos — “clique no botão”, “folha aberta”, “cancelamento” e “fallback de cópia” — e use isso para melhorar. Concentrar o desenho de eventos na implementação de analytics deixa tudo mais legível depois.
P. Qual a diferença entre AbortError e NotAllowedError?
R. O AbortError é o usuário só fechando a folha (não conte como falha). O NotAllowedError é um bloqueio, por exemplo, por ter chamado sem gesto do usuário. Para o segundo, revise a implementação para garantir que a chamada acontece logo após o clique.
P. Quero compartilhar imagens ou PDFs.
R. Você passa um File no array files, mas chame sempre depois de confirmar o suporte com navigator.canShare({ files }). Sem suporte, o seguro é trocar para compartilhar uma URL de download.
O resultado de testar na prática
Depois de distribuir em vários aparelhos, o que ficou claro foi este fato: o valor da Web Share API está menos em “a folha abrir” e mais em “não travar o leitor no aparelho onde ela não abre”. Como manda a condição de uso da MDN, suporte, ausência de suporte e cancelamento se separam de forma limpa, então basta montar na ordem — verificar a existência de navigator.share → sucesso → ignorar o AbortError → o resto, copiar — para o botão sem reação desaparecer.
Ao pedir ao Claude Code, em vez de “faça um botão de compartilhar”, escrever até as condições — “faça um botão de compartilhar que caia para a cópia mesmo em navegadores sem suporte e que não trate o AbortError como erro” — faz voltar, de primeira, um formato pronto para produção. Para quem quer fechar de uma vez o desenho de compartilhamento com o próprio time, em treinamento e consultoria dá para desenhar PWA, medição e fallback de forma integrada. Entregar a matéria-prima é tarefa do sistema; a nossa é “preparar o próximo passo em qualquer aparelho”. Encarando assim, o botão de compartilhar fica bem mais fácil de fazer.
PDF grátis: cheatsheet do Claude Code
Informe seu e-mail e baixe uma página com comandos, hábitos de revisão e workflows seguros.
Cuidamos dos seus dados e não enviamos spam.
Sobre o autor
Masa
Engenheiro focado em workflows práticos com Claude Code.
Artigos relacionados
Como pedir ao Claude Code para mexer em um único arquivo
Do desastre em que um 'deixa melhor' alterou 40 linhas nasceu um template de prompt que limita o escopo, valida e permite reverter.
Recuperar de negações de permissão no Claude Code sem enfraquecer guardrails
Transforme um comando negado em plano seguro com motivo, alternativa, provas e critérios de nova tentativa.
Claude Code Harness Smoke Test: prova de 15 minutos antes de confiar em um agente
Um smoke test para escopo, áreas bloqueadas, comandos de prova, URL pública e CTAs de receita no Claude Code.