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.
Artigos relacionados
Web Audio API com Claude Code: onda, gravação e medidor de volume
Implemente Web Audio API com Claude Code: onda, gravação, medidor, cleanup e testes em React/TypeScript.
Versionamento de API com Claude Code: guia prático para contratos seguros
Planeje versionamento de API com Claude Code: OpenAPI, testes de compatibilidade, headers de depreciação e rollout.
Mock de API com MSW e Claude Code: guia prático
Crie mocks de API realistas com MSW e Claude Code para navegador, testes Node, autenticação, erros e CI.
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.