Skip to main content
A central de ajuda e o chat de suporte numa linha de HTML, em qualquer página — Rails, WordPress, um HTML estático, uma landing page editada num CMS. Sem npm, sem build:
É a mesma central de ajuda, as mesmas conversas, a mesma chave publicável. O que muda é como chega até a página. Com um build de front-end, o <HelpWidget /> do @userkit/react é o mesmo painel como código — com controle de estilo (unstyled), o arranjo inline e os componentes no meio do seu. Para os outros componentes por script — login, conta, planos —, veja Componentes por script.

O que a página paga

Uma requisição, e as outras só quando alguém precisa: O primeiro traz o cliente, o botão e a decisão de desenhá-lo. O segundo é o React e o painel. Quem nunca pede ajuda nunca o baixa; quem tem uma resposta não lida recebe o painel sozinho, porque a prévia da mensagem é justamente o que não pode esperar por um clique — e quem leva o ponteiro até o botão recebe o painel um instante antes do clique, que é o que faz ele abrir na hora. O último são os componentes, e é pequeno porque tudo o que ele divide com o painel — React, o cliente, a folha de estilo — é o arquivo de cima. Uma página sem nenhum [data-userkit] e sem chamada a UserKit.mount() não o baixa, e uma página que usa o widget e um componente baixa o React uma vez.
Instalado por npm, o painel está no seu bundle e é carregado com a página. Por script, ele é buscado quando é preciso — o que torna esta forma mais leve na maioria das páginas, e não uma versão pior.

Onde ele desenha

Num shadow root no fim do <body>. O elemento hospedeiro e as tags <script> que acrescentamos ao <head> são os únicos nós nossos na sua árvore; tudo o que é desenhado fica dentro da fronteira, e o seu CSS não entra nela — nem um * { }, nem um !important em button. É a diferença central em relação ao pacote npm, onde o seu CSS alcança os componentes de propósito. O botão só aparece se houver algo atrás dele: artigos publicados (público, não precisa de sessão) ou o chat ligado com uma sessão de contato. Nenhum dos dois, e a página fica exatamente como estava — um botão na frente do nada abre um pedido de desculpas.

Os componentes, na mesma tag

O widget.js não é só o botão do canto: ele monta os mesmos drop-ins do Componentes por script, na página que já o carrega.
É a mesma tag, a mesma chave e o mesmo cliente — nada de uma segunda linha de <script> para pôr um aviso no topo de uma página que já tinha suporte. Os nomes, os data-* e os eventos são os de lá, e data-auto="false" desliga a montagem automática aqui também.
O userkit.js continua existindo, e a diferença é uma só: ele não tem o botão flutuante. Uma tela de login ou uma página de preços quer os componentes e nada no canto; um produto que já tem o widget instalado não precisa dele. Carregar os dois na mesma página não faz mal, em qualquer ordem: um cliente só, uma sessão só, uma contagem de páginas só, e o widget acrescenta o botão em cima do que o outro arquivo já iniciou. Uma segunda tag de widget.js é ignorada do mesmo jeito.A única regra é que as duas tags levem a mesma data-publishable-key. Uma página é um ambiente: a sessão, os eventos na fila e as chaves guardadas são dele, então uma segunda chave não tem como conviver com a primeira. A tag que nomear outra avisa no console e não faz nada.

Configuração

Tudo por atributo:
data-whats-new é o único que vem desligado, e a razão é uma troca real: a aba alcança quem abre o painel, e o diálogo alcança todo mundo — ao custo de aparecer por cima da página de alguém que entrou para fazer outra coisa. Ligue quando o que você publica precisa ser lido, não só ficar disponível. Os dois não se duplicam: ser exibido é o que marca uma nota como lida, então o que aparecer primeiro esvazia o outro.

Claro, escuro, ou o que a pessoa escolheu

Sem data-theme, o botão e o painel seguem o prefers-color-scheme de quem está lendo — que é o certo na maioria das páginas, e é o que somos há bastante tempo. Não serve para uma aplicação que decide sozinha: se o tema dela é uma classe no <html>, o sistema operacional pode dizer escuro enquanto o interruptor no cabeçalho diz claro, e aí o widget é a única caixa da tela na paleta errada. Não dá para corrigir isso com CSS: o painel vive dentro de um shadow root que a folha de estilo da página não alcança. Por isso é um atributo.
E, para o interruptor que muda de ideia com a página no ar:
Vale para o botão, para o painel e para todo componente montado, existam eles ou não ainda. É um redesenho e não uma remontagem: quem estava no meio de uma mensagem não a perde na troca. Os cinco módulos acima só aparecem quando há o que mostrar: todos pedem sessão, e cada um tem o seu próprio portão — sem nota publicada não há aba Novidades, sem pesquisa pendente não há pesquisa, e uma trilha com todos os passos cumpridos some sozinha. Deixe-os ligados: um visitante anônimo na sua landing page vê exatamente o mesmo painel de antes. Por que aqui eles vêm ligados e no pacote npm vêm desligados: uma página que importa o @userkit/react põe <ChangelogBadge /> e <NotificationBell /> no cabeçalho dela, ao lado do avatar. Esta forma tem um botão e um painel, e nenhum cabeçalho onde pendurar um sino. Uma página que prefere decidir em código omite os atributos e chama init:
O objeto vale mais que o atributo quando os dois falam da mesma coisa: o que foi escrito em código é uma decisão mais recente que a do HTML.

window.UserKit

show() funciona antes de o painel existir: ele é buscado e abre em seguida, o que faz um botão “Ajuda” no seu próprio menu funcionar sem que você espere nada.

Esperar o script

A tag é defer, então a sua página não tem como saber quando ela rodou. ready() é a resposta: uma promessa que resolve com o cliente assim que a sessão deste domínio foi restaurada — ou com null se não havia chave publicável nenhuma.
É uma promessa, e não um evento, de propósito: não existe um instante para perder. Chame antes, durante ou muito depois da inicialização.

Um botão só

Se o seu produto já tem um “Suporte” no cabeçalho, data-launcher="false" tira o nosso do canto. O painel continua intacto — UserKit.show() abre ele do seu botão, que é justamente o motivo de esconder o launcher em vez de não instalar o widget. Para uma decisão que acontece em código — “esconde enquanto este modal está aberto” — use hideLauncher(). Um comando vale mais que o atributo a partir daí, e resetLauncher() é a única volta: um override que expirasse sozinho seria o nosso botão reaparecendo por cima de um checkout por razões que ninguém consegue reconstruir. Com o painel aberto o botão é desenhado de qualquer forma, porque ali ele não é um launcher — é o X do painel.

Desenhar o seu próprio botão

subscribe publica o que o widget sabe sobre si mesmo, que é exatamente o que um botão seu precisa para se desenhar:
available é a mesma resposta que decide se o nosso botão aparece — um botão na frente de nada é um botão que abre um pedido de desculpas. E unread é o mesmo número do nosso badge, contado uma vez só: uma linha que a própria pessoa escreveu nunca conta como não lida, e duas contagens independentes acabariam discordando sobre isso na frente dela.

Identificar quem está na página

O script nunca cria um contato sozinho. Uma tag que começasse a criar linhas para tráfego de landing page seria o pior padrão possível — a decisão é sua, aqui como no pacote npm:
Sem isso, a pessoa vê a central de ajuda publicada e mais nada — que é o correto para quem ainda não entrou. Com o ambiente federado, o hash é o que o seu servidor assinou; nunca gere esse HMAC no navegador.
Se o seu app já usa @userkit/js ou @userkit/react, use o widget do npm em vez deste script. Dois clientes na mesma origem leem a mesma sessão do localStorage e funcionam, mas não se avisam: quem sai numa metade continua dentro na outra até a página recarregar.

CSP

Se o seu site tem Content-Security-Policy, o loader e os arquivos que ele busca precisam de script-src, a API de connect-src, e o logo e os avatares que o seu ambiente serve, de img-src:
A folha de estilo não pede style-src próprio: dentro do shadow root ela é uma folha construída (adoptedStyleSheets), que essa diretiva não governa. Duas coisas ainda dependem de style-src 'unsafe-inline' — um navegador sem folhas construídas, e a paleta escura definida no painel, que os componentes desenham como um <style> dentro da própria árvore. Sem isso o widget desenha em claro, e nada mais se perde. O crossorigin="anonymous" na tag é o que faz um erro nosso chegar ao seu window.onerror com stack em vez de como "Script error.", e o que deixa o script carregar numa página que envia Cross-Origin-Embedder-Policy. Não publicamos hash de SRI para widget.js de propósito: ele é o arquivo que muda para que uma correção chegue à sua página sem você publicar nada, e um hash fixo é exatamente o contrário disso. O painel, esse sim, é imutável — o nome dele carrega o hash do conteúdo.

O que só existe no npm

  • appearance.unstyled e o seu próprio CSS por cima
  • o arranjo inline, dentro do fluxo da página
  • useHelpWidget() e o resto dos hooks
  • passar funções e objetos como props direto no JSX — aqui isso é UserKit.mount() ou um addEventListener no elemento