@userkit/react
entrega os mesmos componentes como código — com tipos, SSR e composição. Os dois
desenham a mesma coisa; a diferença é o que você já tem montado.
Se a sua página já carrega o
widget.js, ela já monta estes componentes — a
mesma marcação, a mesma tag, sem uma segunda linha de <script>. O userkit.js é
para quem quer os componentes e nenhum botão flutuante no canto: uma tela de
login, uma página de preços. O que cada um baixa continua sendo só o que a página
usa — o painel de ajuda não vem junto aqui, e o app dos componentes só é buscado na
primeira montagem lá.Os dois na mesma página funcionam em qualquer ordem: o que rodar primeiro cria
o cliente e o outro o adota — uma sessão, uma fila de eventos, uma contagem de
páginas. Dê a mesma data-publishable-key às duas tags; uma página é um
ambiente, e a tag que nomear uma segunda chave avisa no console e não faz nada.Os nomes são os do pacote
data-userkit="UserButton" monta o que o <UserButton /> renderiza, e os data-*
são as props dele. É de propósito: o dia em que você adotar um build, a migração é uma
reescrita de marcação em JSX com os mesmos nomes — nada de reaprender a API.
Esta tabela é o pacote inteiro. Todo componente que o
@userkit/react exporta
está aqui, e um teste do repositório é o que mantém isso verdadeiro — o que existe com
build existe sem build, pelo mesmo nome.
Os cinco que desenham a SUA marcação
UserButton recebe um elemento vazio e o preenche. Os cinco guardas são o oposto: o que
está dentro do elemento é justamente o que eles decidem mostrar.
addEventListener que a sua
página já tinha pendurado neles continua valendo.
Quando a resposta é não, eles saem da página em vez de ficarem escondidos — o que
importa, porque marcação escondida continua legível para quem abre o inspetor. E como
o fallback do <Flag> é marcação, e atributo não carrega marcação, a versão HTML
não tem esse prop: escreva o caminho antigo num segundo elemento com a flag ao
contrário, ou use UserKit.mount(), que aceita JavaScript de verdade.
Props por atributo
data-redirect-uri vira redirectUri; data-customer="false" vira customer: false;
data-poll-interval-ms="30000" vira número. As três conversões são todas.
data-since="2026-01-01" chega como string, não como
2026.
Quando algo acontece
Um atributo não carrega função, então os retornos que uma página quer usar chegam como evento de DOM, do próprio elemento:
Os eventos sobem (
bubbles), então um ouvinte no document também os pega.
Entrar não é um evento aqui porque não é algo que estes componentes fazem: o seu
próprio auth faz o login, e UserKit.boot({ externalId, hash }) é a chamada
que diz à página quem é a pessoa.
Quando o HTML não basta
Props que não cabem num atributo — uma lista, um objeto, uma função — vão pela API global. Ela existe assim que o script carrega:data-auto="false" desliga a montagem automática, para uma página que prefere
decidir a hora.
O que o mount() devolve
Os componentes chegam da rede, então nada está na tela quando a chamada retorna.
O objeto devolvido responde às duas coisas que se quer disso: dá para esperar
por ele, e dá para usá-lo antes.
update() substitui os props — não mistura com os anteriores — e é uma
re-renderização, não uma remontagem: um formulário meio preenchido sobrevive a
ela. É também como uma função passada por prop continua atual, porque os
handlers são lidos a cada desenho e não congelados no primeiro.
unmount() chamado antes de o pacote chegar cancela a montagem. Isso importa
para quem monta dentro de um framework: um componente que sai da tela enquanto o
pacote está na rede deixaria, sem isso, uma montagem presa num nó que o
framework já soltou.
Um elemento entregue ao
mount() fica reservado na hora, então a varredura
automática não o monta por cima com os data-* dele. Ainda assim, quem monta
em código não precisa marcar o elemento com data-userkit: o nome já vai na
chamada, e marcar os dois é pedir para as duas metades descreverem a mesma
coisa.Saber quando a tag carregou
A tag édefer, então a sua página não tem como saber quando o nosso arquivo
rodou. Os dois loaders avisam ao instalar window.UserKit. Verifique o objeto
antes de ouvir, e não há momento a perder:
detail é o próprio objeto. Numa página que carrega os dois arquivos o
evento dispara duas vezes: o widget.js alarga o objeto que o userkit.js
instalou, e a segunda vez é uma mudança real no que ele sabe fazer. { once: true }
está certo para quem quer mount, que os dois têm, e errado para quem quer show.
React sem colocar os componentes no bundle
Uma app React que carrega o script do CDN tem os dois lados: o React é dela, e os componentes continuam vindo do CDN sob demanda. O pacote@userkit/react-mount é a
ponte — ele não desenha nada, e não importa nada em runtime além do React.
Se a sua app já empacota React e não se importa em empacotar os componentes
também, o
@userkit/react é mais direto —
são componentes de verdade, com os props tipados. Este pacote é para quem quer
especificamente que o desenho pesado continue no CDN.window.UserKit estão publicados em @userkit/js/embed, que não tem
metade em runtime — instale como devDependency e importe com import type:
Montar o @userkit/react dentro de um shadow root
Se você renderiza os componentes do npm dentro de um shadow root seu, envolva
essa árvore num <MountTargetProvider>: assim a folha de estilo cai dentro do
limite e os flutuantes portam para dentro dele também — nenhum dos dois
atravessa um shadow boundary sozinho:
setMountTarget({ styles, portal }), que valia para a página
inteira: uma página pode ter duas árvores — o painel de ajuda no shadow root
dele e um drop-in no seu próprio layout — e uma configuração só fazia a última
montagem decidir pelas duas, então um componente montado depois de abrir o
painel desenhava sem folha de estilo e portava para dentro do limite do painel.
Passar styles ou portal para o setMountTarget hoje emite um aviso no
console e não faz nada; a função continua definindo o nonce do CSP, que é de
fato da página.
Aparência
Os mesmos tokens do widget, no script:data-theme é light, dark ou system (o padrão), e é o que responde por uma
aplicação que decide o próprio tema: sem ele os componentes seguem o
prefers-color-scheme de quem está lendo, que discorda do interruptor no seu
cabeçalho na noite em que o sistema está escuro e a pessoa escolheu claro.
Aqui ele tem dois níveis, porque as perguntas são diferentes. No script vale para
a página; no elemento vale só para aquele componente, e o do elemento vence —
um <PricingTable /> dentro de uma seção escura de uma página clara é uma decisão
de quem escreveu aquela div:
UserKit.setTheme("dark") (ou "light", ou "system")
redesenha todo componente montado — sem remontar, então um formulário meio
preenchido sobrevive à troca. Um elemento com data-theme próprio mantém o seu.
A tipografia herda a da página por padrão, e isso é deliberado: diferente do
widget flutuante, estes componentes não moram dentro de um shadow root. Eles são
colocados por você dentro do seu layout, então devem parecer parte dele — o seu CSS
alcança as classes .uk-*, que é exatamente o que o pacote npm oferece a quem tem
build.
O que a página paga
Uma página que carrega o script e não marca nada não baixa React. Os arquivos com
hash têm o hash do conteúdo no nome e cache de um ano; o primeiro nunca muda de
endereço e é revalidado dentro de uma hora, então uma correção chega à sua
página na navegação seguinte do visitante sem você publicar nada. O arquivo
compartilhado é o mesmo que o widget de ajuda usa, então uma página com os dois
baixa o React uma vez.
Content Security Policy
document.adoptedStyleSheets) no instante em que é
escrita, então style-src não precisa de nada por ela. Um navegador sem folhas
construídas, e a paleta escura definida no painel — que os componentes desenham
como um <style> na própria árvore — ainda pedem style-src 'unsafe-inline';
sem isso os formulários desenham em claro.
O crossorigin="anonymous" na tag é o que faz um erro nosso chegar ao seu
window.onerror com stack em vez de como "Script error.".
O que ele nunca faz
Não cria contato nenhum sozinho.boot() é uma decisão sua aqui como é no pacote —
um <script> que começasse a criar contatos com tráfego de landing page seria o
pior padrão possível.