ZevAuth
Sign up

Building with the SDK

Any framework

The drop-in components for Svelte, Angular, Astro, server-rendered pages and plain HTML, with or without a build step.

@zevauth/ui puts the ZevAuth components on any page, whatever renders the rest of it. It works the same in Svelte, Angular, Solid, a Rails or Django view, or an HTML file with a script tag.

If your app is React, use @zevauth/react instead. If it’s Vue or Nuxt, use @zevauth/vue, which wraps this package.

Install

npm install @zevauth/ui
import { createZevAuthUI } from '@zevauth/ui';

export const zevauth = createZevAuthUI({
  publishableKey: import.meta.env.VITE_ZEVAUTH_KEY,
});

Create the instance once, in a module of its own, and import it wherever you mount a component. Every component from one instance shares one client and one session.

Without a build step

<div id="sign-in"></div>

<script src="https://cdn.jsdelivr.net/npm/@zevauth/ui@0.1.0/dist/zevauth-ui.global.js"></script>
<script>
  ZevAuthUI.init({ publishableKey: 'pk_live_...' });
  ZevAuthUI.mountSignIn('#sign-in', { afterSignInUrl: '/dashboard' });
</script>

init creates one page-wide instance; calling it again returns the same one. The mount… functions on ZevAuthUI use it. Pin the version in the URL so a release you haven’t tested never reaches your users.

Mounting

Every mount… function takes an element, or a selector for one, and the same props as the React component of the same name:

FunctionComponent
mountSignIn<SignIn>
mountSignUp<SignUp>
mountSignInWithZevId<SignInWithZevId>
mountUserButton<UserButton>
mountUserProfile<UserProfile>
mountOrganizationSwitcher<OrganizationSwitcher>
mountOrganizationProfile<OrganizationProfile>

Each returns a handle:

const signIn = zevauth.mountSignIn('#sign-in', { afterSignInUrl: '/dashboard' });

signIn.update({ afterSignInUrl: '/welcome' }); // merged over the current props
signIn.unmount();                              // the element is left empty

Mounting into an element that already holds a component replaces it. That makes it safe to call from code that runs more than once, such as a framework’s mount hook in development.

The element has to be in the page when you mount. A selector that matches nothing throws an error naming the selector.

Props that take React elements, such as aside and children on <SignIn>, are React-only. Everything else, including appearance and localization, passes straight through.

Appearance and wording

Give them to the instance and every component follows. Give them to one mount and only that component changes:

const zevauth = createZevAuthUI({
  publishableKey: 'pk_live_...',
  appearance: { theme: 'soft', variables: { colorPrimary: '#4f46e5' } },
  localization: { signIn: { submit: 'Se connecter' } },
});

zevauth.mountUserButton('#account', { appearance: { elements: { avatar: 'ring-2' } } });

// Later, for everything already on the page, e.g. after a theme switch:
zevauth.update({ appearance: { colorScheme: 'dark' } });

The options are the same as in React. See Customising the components.

Calling your own API

zevauth.client is the client every component uses. Ask it for a token and send it as a bearer token:

const token = await zevauth.client.getToken();

await fetch('/api/projects', {
  headers: { Authorization: `Bearer ${token}` },
});

With the script-tag build, ZevAuthUI.getClient() returns the page-wide client. Verify the token on your server with @zevauth/backend/verify.

Listen for sign-in and sign-out to update the rest of your page:

zevauth.client.addListener(({ isLoaded, user }) => {
  document.body.dataset.signedIn = String(isLoaded && user !== null);
});

In your framework

Mount when the framework has put the element in the page, and unmount when it takes it away.

Svelte

<script>
  import { onMount } from 'svelte';
  import { zevauth } from '$lib/zevauth';

  let element;
  onMount(() => {
    const signIn = zevauth.mountSignIn(element, { afterSignInUrl: '/dashboard' });
    return () => signIn.unmount();
  });
</script>

<div bind:this={element}></div>

Angular

@Component({ selector: 'app-sign-in', template: '<div #host></div>' })
export class SignInComponent implements AfterViewInit, OnDestroy {
  @ViewChild('host') host!: ElementRef<HTMLElement>;
  private mounted?: { unmount(): void };

  ngAfterViewInit() {
    this.mounted = zevauth.mountSignIn(this.host.nativeElement);
  }
  ngOnDestroy() {
    this.mounted?.unmount();
  }
}

Astro: see Astro.

What’s inside

The components are built with React, and this package carries its own private copy. Your page doesn’t need React installed, and a React version elsewhere on the page can’t conflict with it.

That copy is most of the package’s size: about 87 kB gzipped. A React app already has React, which is why it should use @zevauth/react and download none of this twice.

ExportWhat it is
createZevAuthUI(options)An instance: client, the mount… functions, update, destroy
init(options)The page-wide instance, created once
mountSignIn … mountOrganizationProfileMount with the page-wide instance
getClient()The page-wide instance’s client
createZevAuth, ZevAuthErrorThe client factory and error class, from the same copy the components use

options takes publishableKey (or a client), apiUrl, appearance and localization.

Updated at, Sunday, November 1, 2026