npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@userkit/react

v0.4.0

Published

Drop-in UserKit components for React: <SignIn />, <SignUp />, <UserButton />, and the hooks under them.

Readme

@userkit/react

Os componentes prontos do UserKit para React, e os hooks embaixo deles.

npm install @userkit/react

Versão 0.3.0, acompanhando o @userkit/js. Os três pacotes são versionados juntos: um @userkit/react 0.2.x sempre pede o @userkit/js 0.2.x.

Uso

import { UserKitProvider, SignIn, SignedIn, SignedOut, UserButton } from "@userkit/react";

export default function App() {
  return (
    <UserKitProvider publishableKey="uk_pk_live_…">
      <header>
        <SignedIn><UserButton /></SignedIn>
      </header>
      <SignedOut><SignIn /></SignedOut>
    </UserKitProvider>
  );
}

É isso. O <SignIn /> pergunta à API o que aquele ambiente suporta — então ligar o Google no painel acende o botão sem ninguém fazer deploy — e a página de callback do login social é a mesma página, porque o provider termina o fluxo sozinho quando encontra um ?code= que ele mesmo iniciou.

Os guardas

<SessionLoading><Skeleton /></SessionLoading>
<SignedIn><App /></SignedIn>
<SignedOut><SignIn /></SignedOut>
<Verified><Faturas /></Verified>

Nenhum dos dois primeiros renderiza enquanto a sessão está sendo restaurada. É para isso que existe o terceiro estado: uma página que mostra "deslogado" por um frame a cada recarga é o defeito mais visível que uma biblioteca de autenticação pode ter — e ele é invisível numa conexão rápida durante o desenvolvimento.

<Verified> é para sessão com identidade provada. Ela é cortesia; quem impede de verdade é o requireVerifiedContact do outro lado.

Os hooks

const { client, state, config } = useUserKitContext();
const contact = useContact();
const session = useSession();

Os componentes são conveniência sobre os hooks, não uma parede na frente deles. No momento em que o <SignIn /> não é o formato que você quer, você escreve o formulário e mantém todo o resto.

A conta e os times

<header>
  <SignedIn>
    <CustomerSwitcher onSwitch={(customer) => router.push(`/${customer.id}`)} />
    <UserButton />
  </SignedIn>
</header>
<SignedIn><AccountSettings /></SignedIn>

O <CustomerSwitcher /> não escreve nada ao trocar de time: ele muda o X-Customer-Id que vai nas chamadas seguintes, e a associação por trás do header é quem autoriza. Criar um time é a única escrita que ele tem.

Sobre o nome: o que ele troca é um customer — a entidade que paga, um customer com vários contatos — e o componente se chama pelo que troca, como tudo que ele toca (o callback recebe um CustomerMembership). OrganizationSwitcher e OrganizationProfile existem como alias do mesmo componente, porque é a palavra que quem vem de outro SDK digita quando vai procurar por ele.

O <AccountSettings /> renderiza o que o ambiente e a sessão realmente podem fazer: num ambiente federado não aparece senha nem verificação em duas etapas — a API responde 409 nas duas —, e o que está atrás do requireVerifiedContact (dispositivos, segundo fator, equipe) só aparece para sessão com identidade provada.

Flags e engajamento

<Flag name="new_checkout" fallback={<CheckoutAntigo />}>
  <CheckoutNovo />
</Flag>

const ligada = useFlag("new_checkout");   // boolean, nunca null, nunca promise

useFlag devolve um booleano e ponto: um hook que começasse em undefined faria todo recurso com flag renderizar sua ausência por um frame e aparecer no seguinte — o mesmo defeito que o <SessionLoading> existe para evitar uma camada acima, só que aqui não há terceiro estado para desenhar, porque "ainda não sei" e "desligado" são idênticos para a pessoa e só um dos dois é seguro. <Flag> é cortesia como o <Verified>: quem impede de verdade é o servidor.

<OnboardingChecklist />
<ChangelogBadge />
<Survey />
<FeedbackButton />

Cada um renderiza o que o ambiente consegue responder e nada além. O checklist não tem botão que marque um passo — não existe endpoint que faça isso, porque um passo é satisfeito por um fato ter acontecido ou por um direito do plano estar valendo. O <Survey /> não tem prop que peça uma pesquisa: quem decide quem é perguntado é a API — o que ele tem é position: center (padrão), bottom-right, bottom-left ou inline. Só o centro escurece a página e toma o foco; os cantos flutuam sem backdrop, porque escurecer o produto para perguntar sobre o produto é o que faz um NPS de cinco segundos parecer um pedágio. Esc e o clique fora são "agora não" e não gastam a entrega.

O <FeedbackButton /> aceita as mesmas quatro posições, com inline de padrão — e nas três flutuantes o que fica no seu layout é só o botão, porque um header tem espaço para um botão e não para uma lista de ideias com um formulário embaixo. E abrir o <ChangelogBadge /> é o que marca como lidos os posts que ele de fato mostrou — a API recusa um post que o leitor nunca viu, em vez de deixar um badge ser zerado por uuid.

<WhatsNew />

O mesmo changelog pelo outro lado. O badge espera ser clicado; este abre sozinho — "Enquanto você esteve fora" — com o que aquele contato não viu, e não desenha nada quando não há nada. É a ordem das duas perguntas que garante isso: ele lê o contador primeiro e, num zero, acaba ali — a lista nem chega a ser pedida. O conjunto que ele mostra é o do próprio contador: não lido e publicado depois de since, que é quando aquele contato chegou. Quem chegou hoje não esteve fora de nada, e vê o diálogo que isso merece, que é nenhum.

Ser mostrado é o que marca esses posts como lidos, senão o mesmo diálogo cumprimenta a mesma pessoa amanhã com os mesmos posts. E o corpo aqui é markdown de verdade, via <HelpMarkdown /> — que constrói elementos e nunca HTML, então a prosa do inquilino chegando pela rede não tem onde injetar.

Todas as frases passam pelo dicionário (localization), incluindo os rótulos acessíveis: um checklist lido em voz alta precisa dizer quais passos estão concluídos, e cor sozinha não é um estado.

As notificações dentro do produto

<NotificationBell />

const { notifications, unread, markRead, markAllRead } = useNotifications();

O que aparece aqui é o que o seu backend endereçou a esta pessoa sobre o trabalho dela — "seu export está pronto", "a fatura falhou" — escrito em POST /v1/notifications com a chave uk_sk_. Não há metade que componha uma: uma rota alcançável pelo navegador deixaria qualquer página dizer qualquer coisa a qualquer usuário seu.

Abrir o sino não marca nada como lido, e essa é a diferença para o <ChangelogBadge />. Um post de changelog é um anúncio que todo mundo recebe, e abrir a lista é honestamente o ato de ler; uma notificação é endereçada a uma pessoa sobre uma coisa, e zerar dez porque alguém olhou um dropdown apaga exatamente o estado que ela veio conferir. Cada linha é marcada quando é acionada, e "marcar todas" é um botão que se aperta de propósito.

O painel abre como uma gaveta lateral, num portal para fora da árvore do componente: o sino costuma morar num header sticky, e um menu absoluto ali dentro é recortado por um overflow que este componente não enxerga.

Uma notificação pode levar a algum lugar — url, escrito pelo seu backend junto com o título. A linha vira link, e numa SPA o onNavigate entrega o caminho ao seu roteador em vez de recarregar a página:

<NotificationBell onNavigate={(url) => router.push(url)} />

Um endereço de outra origem — o seu segundo domínio — abre em aba nova com rel="noopener noreferrer" e nunca passa pelo onNavigate. Um clique com modificador fica com o navegador, então o botão do meio continua abrindo aba.

O poll roda só com a aba visível — um minuto por padrão, e nada enquanto a página está escondida: /v1/contact/* dá 600 requisições por hora por sessão, e uma aba de fundo perguntando para sempre é bateria de terceiros gasta num badge que ninguém está olhando. Quando a aba volta, ele pergunta na hora em vez de esperar o intervalo que dormiu.

Aparência

<SignIn
  appearance={{
    logoUrl: "/logo.svg",
    variables: { colorPrimary: "#5b21b6", borderRadius: "0.75rem" },
  }}
/>

A lista de tokens é curta de propósito: estes componentes renderizam na página do desenvolvedor, então o que importa é o punhado de variáveis que faz eles pertencerem ali — não um segundo design system embarcado numa biblioteca de auth. Além disso, appearance={{ unstyled: true }} não injeta folha de estilo nenhuma e deixa os nomes de classe (uk-*) para o seu CSS.

O tema escuro segue o prefers-color-scheme por padrão.

O que não está aqui

Nada aqui edita nome, e-mail ou avatar: /v1/contact/* não tem rota que faça isso, e um componente para uma superfície que a API não tem é promessa, não funcionalidade. Trocar a senha, pela mesma razão, é um link enviado para o e-mail — é o único caminho que existe, e é ele que prova o endereço.

Estilo, tema e CSP

A folha de estilo é injetada pelo pacote — não há .css para importar. Em React 19 ela vai como <style href precedence>: o React a sobe para o <head>, deduplica entre todos os componentes e a emite no servidor, então uma página renderizada no Next chega com o formulário já estilizado. Em React 18 não existe hoisting: a folha entra por JavaScript no primeiro render do cliente, e o primeiro paint de uma página SSR é um formulário sem estilo até a hidratação.

Os tokens (--uk-color-primary, --uk-radius, …) têm o padrão claro na própria folha e o escuro num bloco prefers-color-scheme: dark. O elemento só carrega os tokens que alguém definiu — o appearance.variables ou a paleta do tenant — e por isso o modo escuro funciona: um valor inline vence qualquer regra, e um padrão inline seria um bloco escuro que nunca se aplica.

<SignIn appearance={{ theme: "dark" }} />        // decide aqui, ignora o sistema
<SignIn appearance={{ theme: "light" }} />
<SignIn appearance={{ variables: { colorPrimary: "#0b5cff" } }} />

appearance.theme escreve data-uk-theme na raiz — é o que um app com o tema numa classe do <html> (Tailwind, shadcn) precisa, porque o sistema pode dizer uma coisa e o botão no cabeçalho outra.

unstyled é por página, não por componente: a folha é uma só, e as classes são as mesmas. Um <SignIn appearance={{ unstyled: true }} /> ao lado de um <UserButton /> estilizado continua recebendo as regras que o outro trouxe.

Content-Security-Policy

Uma style-src sem 'unsafe-inline' bloqueia a folha em silêncio. Passe o nonce da página:

<UserKitProvider publishableKey="…" nonce={nonce}>

No cliente ele vai para todo <style> que o pacote escreve. No servidor o React descarta o atributo de um <style> hoisted e aplica a opção do próprio render — nonce: { style } em renderToPipeableStream/renderToReadableStream (React 19.1+), que o Next preenche a partir do cabeçalho CSP da página. Fora do React, setMountTarget({ nonce }) é a mesma configuração.

Avançado

  • UserKitContext é exportado para um teste que quer entregar um valor pronto aos componentes. Em código de produto use useUserKitContext(), que lança fora de um provider em vez de devolver null.
  • @userkit/react/locales entrega ptBR e enUS como dados, sem a diretiva "use client" — para um Server Component que quer ler uma frase.
  • useOnboardingGuide(), useHelpWidget(), useNotificationBell() e usePoints() são o estado dos drop-ins, para um botão seu.
  • Position é o vocabulário de posição dos flutuantes (center, bottom-right, bottom-center, bottom-left, inline); Position, Position e Position são o mesmo tipo com os nomes que já saíram.
  • O pacote é ESM-only: não há require. Jest em CJS puro precisa de transformIgnorePatterns para @userkit/.
  • As props do <UserKitProvider> são um tipo largo, não uma união das três formas (publishableKey, mode: "proxy", client): o @userkit/nextjs envolve o componente e espalha as próprias props, e uma união discriminada não sobrevive a um Omit mais um spread. Faltar as três é um erro em runtime, na construção do client.