> ## Documentation Index
> Fetch the complete documentation index at: https://docs.userkit.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Widget por script

> A central de ajuda e o chat de suporte numa linha de HTML — sem npm, sem build, em qualquer página.

**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:

```html theme={null}
<script
  src="https://cdn.userkit.dev/widget.js"
  data-publishable-key="uk_pk_live_…"
  defer
  crossorigin="anonymous"
></script>
```

É 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](/pt-br/guides/components-script).

## O que a página paga

Uma requisição, e as outras só quando alguém precisa:

| Arquivo              | Tamanho       | Quando                                                                                               |
| -------------------- | ------------- | ---------------------------------------------------------------------------------------------------- |
| `widget.js`          | \~14 KB gzip  | sempre                                                                                               |
| `v/vendor-<hash>.js` | \~115 KB gzip | no primeiro clique, quando o ponteiro chega ao botão, ou antes disso se já houver resposta esperando |
| `w/app-<hash>.js`    | \~1 KB gzip   | junto — a entrada do painel                                                                          |
| `u/app-<hash>.js`    | \~16 KB gzip  | na primeira montagem de componente, se a sua página montar algum                                     |

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**.

<Note>
  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.
</Note>

## 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](/pt-br/guides/components-script), na página que já o
carrega.

```html theme={null}
<div data-userkit="Banner" data-slot="dashboard-top"></div>

<script
  src="https://cdn.userkit.dev/widget.js"
  data-publishable-key="uk_pk_live_…"
  defer
  crossorigin="anonymous"
></script>
```

É 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.

<Note>
  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.
</Note>

## Configuração

Tudo por atributo:

```html theme={null}
<script
  src="https://cdn.userkit.dev/widget.js"
  data-publishable-key="uk_pk_live_…"
  data-position="bottom-left"
  data-locale="en"
  data-theme="light"
  data-color-primary="#0b5cff"
  data-border-radius="0.75rem"
  data-font-family="Inter, sans-serif"
  data-articles="false"
  defer
  crossorigin="anonymous"
></script>
```

| Atributo               | Padrão           | O que faz                                                                                       |
| ---------------------- | ---------------- | ----------------------------------------------------------------------------------------------- |
| `data-publishable-key` | —                | Obrigatório. `uk_pk_live_…` ou `uk_pk_test_…`                                                   |
| `data-position`        | `bottom-right`   | Canto do botão e do painel                                                                      |
| `data-locale`          | `pt-BR`          | `pt-BR` ou `en`                                                                                 |
| `data-theme`           | `system`         | `light`, `dark` ou `system`. `light` desliga o modo escuro; `dark` força                        |
| `data-color-primary`   | `#111827`        | Cor de marca                                                                                    |
| `data-border-radius`   | `0.5rem`         | Raio dos cantos                                                                                 |
| `data-font-family`     | herda da página  | Tipografia do painel                                                                            |
| `data-articles`        | `true`           | Desenha a central publicada                                                                     |
| `data-launcher`        | `true`           | Desenha **o nosso botão flutuante**. `false` para quem já tem um "Suporte" no próprio cabeçalho |
| `data-changelog`       | `true`           | Aba **Novidades**: as notas de versão que esta pessoa ainda não leu                             |
| `data-notifications`   | `true`           | Aba **Notificações**: o que o seu backend endereçou a ela                                       |
| `data-feedback`        | `true`           | Aba **Ideias**: o mural de sugestões, com o voto dela                                           |
| `data-checklist`       | `true`           | A trilha de primeiros passos no topo do painel, enquanto houver passo pendente                  |
| `data-surveys`         | `true`           | A pesquisa que a API decidiu fazer a esta pessoa, no topo do painel                             |
| `data-whats-new`       | `false`          | Além da aba, abre as notas não lidas como **diálogo**, sozinho                                  |
| `data-pageviews`       | —                | `auto` emite `page_viewed` na carga e a cada navegação                                          |
| `data-api-url`         | a API da UserKit | Só para quem faz proxy                                                                          |

`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.

```html theme={null}
<!-- o modo escuro desligado, aconteça o que acontecer no sistema -->
<script src="https://cdn.userkit.dev/widget.js" data-theme="light" …></script>
```

E, para o interruptor que muda de ideia com a página no ar:

```js theme={null}
UserKit.setTheme("dark");    // "light", "dark" ou "system"
```

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`:

```html theme={null}
<script src="https://cdn.userkit.dev/widget.js" defer crossorigin="anonymous"></script>
<script>
  window.userkitSettings = { publishableKey: "uk_pk_live_…", position: "bottom-left" };
</script>
```

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`

```js theme={null}
UserKit.show();                       // abre o painel
UserKit.hide();
UserKit.toggle();
UserKit.showArticle("como-funcionam-os-reembolsos");
UserKit.showNewMessage("Meu cartão foi recusado");
await UserKit.boot({ externalId: "usr_42", hash: "…" });

await UserKit.mount("#aviso", "Banner", { slot: "dashboard-top" });
await UserKit.mountAll();             // varre a página de novo, depois de trocar o DOM
UserKit.destroy();                    // tira tudo da página: painel, botão e componentes

const client = await UserKit.ready(); // o cliente, com a sessão já restaurada
UserKit.hideLauncher();               // esconde o nosso botão; o painel continua abrindo
UserKit.showLauncher();
UserKit.resetLauncher();              // devolve a decisão para `data-launcher`
UserKit.setTheme("dark");             // "light", "dark" ou "system", com a página no ar
UserKit.getState();                   // { open, unread, available, launcherVisible }
UserKit.subscribe((state) => {});     // segue os quatro
```

`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.

```js theme={null}
const client = await UserKit.ready();
client?.track("pricing_viewed");
```

É 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:

```js theme={null}
UserKit.subscribe(({ available, unread, open, launcherVisible }) => {
  meuBotao.hidden = !available;        // nada publicado, nenhum chat: não desenhe nada
  meuBadge.textContent = unread || "";
});
```

`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:

```js theme={null}
await UserKit.boot({ externalId: "usr_42", email: "grace@example.com", hash: "…" });
```

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.

<Warning>
  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.
</Warning>

## 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`:

```
script-src  'self' https://cdn.userkit.dev;
connect-src 'self' https://api.userkit.dev;
img-src     'self' https: data:;
```

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
