BETTER-AUTH. UI
Components

<AuthProvider />

Provides AuthConfig and Solid Query context to installed Solid auth components.

Usage

Install the provider registry payload with npx shadcn@latest add https://better-auth-ui.com/r/solid/auth-provider.json. It copies src/components/auth/auth-provider.tsx and related provider support files. After install, these files are app-owned. Then wrap your application shell as shown in the Solid/Zaidan example.

import { deleteUserPlugin } from "@better-auth-ui/core/plugins/delete-user"
import type { AuthLinkProps } from "@better-auth-ui/solid"
import type { QueryClient } from "@tanstack/solid-query"
import {
  Link as RouterLink,
  useNavigate,
  useParams
} from "@tanstack/solid-router"
import type { JSX } from "solid-js"
import { onCleanup, onMount, Show, splitProps } from "solid-js"
import { apiKeyPlugin } from "@/lib/auth/api-key-plugin"
import { emailOtpPlugin } from "@/lib/auth/email-otp-plugin"
import { magicLinkPlugin } from "@/lib/auth/magic-link-plugin"
import { multiSessionPlugin } from "@/lib/auth/multi-session-plugin"
import { organizationPlugin } from "@/lib/auth/organization-plugin"
import { passkeyPlugin } from "@/lib/auth/passkey-plugin"
import { themePlugin } from "@/lib/auth/theme-plugin"
import { twoFactorPlugin } from "@/lib/auth/two-factor-plugin"
import { usernamePlugin } from "@/lib/auth/username-plugin"
import { authClient } from "@/lib/auth-client"
import { syncDocumentThemePreference } from "@/lib/theme"

import { AuthProvider } from "./auth/auth-provider"
import { Toaster } from "./ui/sonner"

export type ProvidersProps = {
  children?: JSX.Element | (() => JSX.Element)
  queryClient?: QueryClient
}

const resolveProviderChildren = (children: ProvidersProps["children"]) =>
  typeof children === "function" ? children() : children

function AuthLink(props: AuthLinkProps) {
  const [local, linkProps] = splitProps(props, ["href"])

  return <RouterLink {...linkProps} to={local.href} />
}

export function Providers(props: ProvidersProps) {
  const navigate = useNavigate()
  const params = useParams({ strict: false })
  const organizationSlug = () => {
    const slug = params()?.slug

    if (typeof slug === "string" && slug.length > 0) return slug

    return null
  }

  onMount(() => {
    const cleanup = syncDocumentThemePreference()

    onCleanup(cleanup)
  })

  return (
    <Show keyed when={organizationSlug() ?? "personal"}>
      <AuthProvider
        authClient={authClient}
        Link={AuthLink}
        redirectTo="/settings/account"
        navigate={navigate}
        queryClient={props.queryClient}
        socialProviders={["github"]}
        plugins={[
          multiSessionPlugin(),
          apiKeyPlugin({
            organization: true,
            configurations: [
              { id: "default", label: "Personal", organization: false },
              { id: "organization", label: "Organization", organization: true }
            ]
          }),
          usernamePlugin({
            usernamePrefix: "@",
            localization: { usernamePlaceholder: "username" }
          }),
          magicLinkPlugin(),
          emailOtpPlugin({
            emailVerification: true,
            passwordReset: true,
            changeEmail: true
          }),
          twoFactorPlugin(),
          passkeyPlugin(),
          themePlugin(),
          deleteUserPlugin(),
          organizationPlugin({
            slugPrefix: "@",
            slug: organizationSlug(),
            teams: true
          })
        ]}
      >
        {() => (
          <>
            {resolveProviderChildren(props.children)}
            <Toaster />
          </>
        )}
      </AuthProvider>
    </Show>
  )
}

Localization

Install the locale package:

bun add @better-auth-ui/locales

Import one locale and pass it to AuthProvider:

components/providers.tsx
import { deDE } from "@better-auth-ui/locales/de-DE"

<AuthProvider authClient={authClient} locale={deDE}>
  {children}
</AuthProvider>

Locale bundles include the core messages and all built-in plugin messages.

Use localization for product-specific text. These values take priority over the selected locale:

<AuthProvider
  authClient={authClient}
  locale={deDE}
  localization={{ auth: { signIn: "Bei Acme anmelden" } }}
>
  {children}
</AuthProvider>

Match the browser language

In a client-only application, import the supported locales and match navigator.languages against that list:

import { matchAuthLocale } from "@better-auth-ui/locales"
import { deDE } from "@better-auth-ui/locales/de-DE"
import { enUS } from "@better-auth-ui/locales/en-US"

const locale = matchAuthLocale({
  requested: navigator.languages,
  supported: [enUS, deDE],
  fallback: enUS
})

For server rendering, resolve the same locale from a user preference or the Accept-Language header. Pass that locale during the first render to prevent a hydration mismatch.

Changing the locale prop updates mounted auth components. Email components do not read AuthProvider; pass their localization on the server.

Set socialSignInMode="popup" to keep the current page open during social sign-in. Redirect mode remains the default.

Better Auth marks this API as experimental. Configure the server and client plugins before you enable it:

lib/auth.ts
import { betterAuth } from "better-auth"
import { bearer, oauthPopup } from "better-auth/plugins"

export const auth = betterAuth({
  plugins: [bearer(), oauthPopup()]
})
lib/auth-client.ts
import { createAuthClient } from "better-auth/solid"
import { oauthPopupClient } from "better-auth/client/plugins"

export const authClient = createAuthClient({
  plugins: [oauthPopupClient()]
})

Then select popup mode on the provider:

<AuthProvider
  authClient={authClient}
  navigate={navigate}
  socialProviders={["github", "google"]}
  socialSignInMode="popup"
>
  {children}
</AuthProvider>

Popup mode uses the same provider buttons and redirect target. It returns control to the current page, refreshes the session, and then runs the configured navigation.

Props

Prop

Type

Last updated on

On this page