Pre-built, customizable authentication UI components for Better Auth in Svelte 5.
This is a complete Svelte 5 port of the Better Auth UI React library with full feature parity. The API is nearly identical to the original React library, making it easy to follow the official documentation. All credits for the original design and architecture go to daveycodez.
Important: This library is in early stage development. While we have achieved full feature parity with the React version and all components have been ported, the library has not been battle-tested in production environments yet. Issues may arise. Use at your own risk until we reach v1.0 stable.
This Svelte port was primarily built to support my own projects including stacksee.com and textatlas.com. While it currently maintains full feature parity with the original React library, this port may evolve independently over time. I may add new features, make different architectural decisions, or implement functionality specifically tailored to my project needs that diverge from the original library.
If you're looking for a library that strictly mirrors the React version, please be aware that this port's API and features may change to better serve the needs of my projects and the Svelte ecosystem.
Ensure you have Better Auth and shadcn set up in your project first.
Them install the package:
pnpm add better-auth-ui-svelte
## bun add better-auth-ui-svelte
## npm install better-auth-ui-svelte
## yarn add better-auth-ui-svelte
Make sure you have these installed in your project:
svelte ^5.0.0better-auth ^1.3.0bits-ui ^2.0.0@lucide/svelte ^0.400.0tailwindcss ^4.0.0zod ^4.0.0svelte-sonner ^0.4.0For TailwindCSS v4, add the following @import to your global CSS file:
/* app.css or global.css */
@import 'better-auth-ui-svelte/css';
For TailwindCSS v3 (Deprecated), add the following to your Tailwind config:
content: ['./node_modules/better-auth-ui-svelte/dist/**/*.{js,svelte}'];
Follow these steps to integrate Better Auth UI into your SvelteKit project:
Create your Better Auth client instance (e.g., in src/lib/auth-client.ts):
import { createAuthClient } from 'better-auth/svelte';
export const authClient = createAuthClient({
baseURL: 'http://localhost:5173',
// Add any plugins you need
plugins: [
// organizationClient(),
// twoFactorClient(),
// etc.
]
});
The <AuthUIProvider /> wraps your application with authentication context. Set it up in your root layout:
<!-- src/routes/+layout.svelte -->
<script lang="ts">
import { AuthUIProvider } from 'better-auth-ui-svelte';
import { Toaster } from 'svelte-sonner';
import { authClient } from '$lib/auth-client';
import { goto, invalidateAll } from '$app/navigation';
</script>
<Toaster />
<AuthUIProvider {authClient}>
{@render children()}
</AuthUIProvider>
The <AuthUIProvider /> can be customized with additional settings:
<AuthUIProvider
{authClient}
navigate={goto}
onSessionChange={async () => await invalidateAll()}
social={{
providers: ['github', 'google', 'facebook']
}}
magicLink
passkey
multiSession
twoFactor={['otp', 'totp']}
>
<slot />
</AuthUIProvider>
Create a dynamic route to handle all authentication views. Create the file src/routes/auth/[path]/+page.svelte:
<script lang="ts">
import { AuthView, authViewPaths } from 'better-auth-ui-svelte';
import type { PageProps } from './$types.js';
let { data, params }: PageProps = $props();
</script>
<main class="container flex grow items-center justify-center p-4">
<AuthView path={params.path} />
</main>
This single dynamic route handles all authentication views:
/auth/sign-in – Sign in via email/password and social providers/auth/sign-up – New account registration/auth/magic-link – Email login without a password/auth/forgot-password – Request password reset email/auth/reset-password – Set new password after receiving reset link/auth/two-factor – Two-factor authentication/auth/recover-account – Recover account via backup code/auth/email-otp – Email OTP verification/auth/sign-out – Log the user out/auth/callback – OAuth callback handler/auth/accept-invitation – Accept organization invitationYou can use individual authentication forms directly in your pages:
<script lang="ts">
import { SignInForm, SignUpForm } from 'better-auth-ui-svelte';
</script>
<SignInForm
redirectTo="/dashboard"
onSuccess={() => {
console.log('Signed in!');
}}
/>
Use <SignedIn /> and <SignedOut /> to conditionally render content:
<script lang="ts">
import { SignedIn, SignedOut } from 'better-auth-ui-svelte';
</script>
<SignedOut>
<a href="/auth/sign-in">Sign In</a>
</SignedOut>
<SignedIn>
<a href="/dashboard">Dashboard</a>
</SignedIn>
Display a user button with avatar and dropdown menu:
<script lang="ts">
import { UserButton } from 'better-auth-ui-svelte';
</script>
<UserButton align="end" />
<AuthView /> - Dynamic authentication view handler (sign in, sign up, forgot password, etc.)<AuthUIProvider /> - Context provider for auth configuration<AuthLoading /> - Loading state component<SignInForm /> - Email/password sign in<SignUpForm /> - User registration<ForgotPasswordForm /> - Password reset request<ResetPasswordForm /> - Set new password<MagicLinkForm /> - Magic link authentication<TwoFactorForm /> - Two-factor authentication<RecoverAccountForm /> - Account recovery<EmailOtpForm /> - Email OTP verification<UserButton /> - User dropdown menu with avatar<UserAvatar /> - User avatar with fallback to initials<SignedIn /> - Renders children only when authenticated<SignedOut /> - Renders children only when not authenticated<AccountSettingsCards /> - Complete account settings UI<AccountsCard /> - Manage connected accounts<UpdateAvatarCard /> - Avatar upload and management<UpdateNameCard /> - Update user name<UpdateUsernameCard /> - Update username<UpdateFieldCard /> - Generic field update card<DeleteAccountCard /> - Account deletion with confirmation<SecuritySettingsCards /> - Complete security settings UI<ChangeEmailCard /> - Email change with verification<ChangePasswordCard /> - Password change with validation<SessionsCard /> - Active sessions management<PasskeysCard /> - Passkey authentication management<TwoFactorCard /> - Two-factor authentication setup<ApiKeysCard /> - API key management<OrganizationSwitcher /> - Switch between organizations<CreateOrganizationDialog /> - Create new organization<OrganizationView /> - Organization details view<OrganizationSettingsCards /> - Organization settings<OrganizationMembersCard /> - Member management<OrganizationInvitationsCard /> - Invitation management<UserInvitationsCard /> - User's received invitations<AuthCallback /> - OAuth callback handler<SignOut /> - Sign out component<RedirectToSignIn /> - Redirect unauthenticated users to sign in<RedirectToSignUp /> - Redirect users to sign up<PasswordInput /> - Password input with visibility toggle<FormError /> - Form error display componentCustomize all text strings by passing a localization prop to <AuthUIProvider />:
<script lang="ts">
import { AuthUIProvider, authLocalization } from 'better-auth-ui-svelte';
import { authClient } from '$lib/auth-client';
const customLocalization = {
...authLocalization,
SIGN_IN: 'Log In',
SIGN_UP: 'Create Account',
EMAIL: 'Email Address',
PASSWORD: 'Your Password'
};
</script>
<AuthUIProvider {authClient} localization={customLocalization}>
<slot />
</AuthUIProvider>
All components accept a className prop for custom styling:
<SignInForm className="max-w-md mx-auto" />
For granular styling control, use the classNames prop:
<AuthView
path="sign-in"
classNames={{
base: 'border-2 border-primary',
header: 'bg-primary/10',
title: 'text-xl font-bold',
footerLink: 'text-primary hover:underline'
}}
/>
The <AuthUIProvider /> accepts the following configuration options:
interface AuthUIProviderProps {
// Required
authClient: ReturnType<typeof createAuthClient>;
// Navigation (SvelteKit)
navigate?: (href: string) => void;
// Session management
onSessionChange?: () => void | Promise<void>;
// Social authentication
social?: {
providers?: ('google' | 'github' | 'facebook' | 'apple' | 'discord' | 'twitter')[];
};
// Additional auth methods
magicLink?: boolean | {
resendCooldown?: number;
redirectToSentPage?: boolean;
};
emailVerification?: boolean | {
resendCooldown?: number;
redirectToVerifyPage?: boolean;
};
passkey?: boolean;
// Two-factor authentication
twoFactor?: ('otp' | 'totp')[];
// Multi-session support
multiSession?: boolean;
// Localization
localization?: Partial<AuthLocalization>;
// Avatar handling
avatar?: {
upload?: (file: File) => Promise<string>;
delete?: (url: string) => Promise<void>;
};
// Settings page configuration
settings?: {
url?: string;
};
// Organization configuration
organization?: {
pathMode?: 'id' | 'slug';
basePath?: string;
slug?: string;
};
}
Note: This feature is unique to the Svelte port and not available in the original React version of Better Auth UI. Added by Chris Jayden to ease server-side implementation in SvelteKit.
Better Auth UI for Svelte includes utility functions to generate authentication paths that work both client-side and server-side. This makes it easy to maintain consistent paths across your application and handle redirects in hooks, load functions, and components.
hooks.server.ts, +page.server.ts, and +layout.server.ts/auth/sign-in paths againimport { getAuthPath, getAuthUrl } from 'better-auth-ui-svelte';
// Get auth paths (default: '/auth')
getAuthPath('SIGN_IN'); // '/auth/sign-in'
getAuthPath('SIGN_UP'); // '/auth/sign-up'
getAuthPath('FORGOT_PASSWORD'); // '/auth/forgot-password'
// Get full URLs (useful for emails, redirects)
getAuthUrl('RESET_PASSWORD', {
baseURL: 'https://example.com'
}); // 'https://example.com/auth/reset-password'
// Account and organization paths
getAccountPath('SETTINGS'); // '/account/settings'
getOrganizationPath('MEMBERS'); // '/organization/members'
Use path helpers in your server hooks for authentication redirects:
// src/hooks.server.ts
import { redirect } from '@sveltejs/kit';
import { getAuthPath } from 'better-auth-ui-svelte';
export async function handle({ event, resolve }) {
const session = await getSession(event);
// Redirect unauthenticated users to sign-in
if (!session && event.url.pathname.startsWith('/app')) {
throw redirect(303, getAuthPath('SIGN_IN'));
}
// Redirect authenticated users away from auth pages
if (session && event.url.pathname === getAuthPath('SIGN_IN')) {
throw redirect(303, '/app');
}
return resolve(event);
}
Create a shared config file to keep paths consistent across your app:
// src/lib/config/auth-config.ts
import type { PathConfig } from 'better-auth-ui-svelte';
export const authPathConfig: PathConfig = {
basePath: '/auth',
// Optionally customize individual paths
viewPaths: {
SIGN_IN: 'login', // /auth/login instead of /auth/sign-in
SIGN_UP: 'register' // /auth/register instead of /auth/sign-up
}
};
Then use it everywhere:
<!-- src/routes/+layout.svelte -->
<script lang="ts">
import { AuthUIProvider } from 'better-auth-ui-svelte';
import { authPathConfig } from '$lib/config/auth-config';
// Provider will use your custom paths
<AuthUIProvider
{authClient}
basePath={authPathConfig.basePath}
viewPaths={authPathConfig.viewPaths}
/>
</script>
// src/hooks.server.ts
import { getAuthPath } from 'better-auth-ui-svelte';
import { authPathConfig } from '$lib/config/auth-config';
// Use same config for server-side redirects
throw redirect(303, getAuthPath('SIGN_IN', authPathConfig));
// Auth paths
getAuthPath(view, config?) // Get auth path
getAuthUrl(view, config?) // Get full URL with baseURL
getAllAuthPaths(config?) // Get all auth paths as object
// Account paths
getAccountPath(view, config?)
getAccountUrl(view, config?)
getAllAccountPaths(config?)
// Organization paths
getOrganizationPath(view, config?)
getOrganizationUrl(view, config?)
getAllOrganizationPaths(config?)
The library exports the following utilities:
// Component exports
export { AuthView, AuthUIProvider, SignInForm, SignUpForm, UserButton, UserAvatar, ... };
// Path constants
export { authViewPaths, accountViewPaths, organizationViewPaths };
// Path helpers (unique to Svelte port)
export {
getAuthPath,
getAuthUrl,
getAccountPath,
getAccountUrl,
getOrganizationPath,
getOrganizationUrl,
getAllAuthPaths,
getAllAccountPaths,
getAllOrganizationPaths
};
// Utilities
export { createForm, getViewByPath };
// Context helpers
export { getAuthUIConfig, getAuthClient, getLocalization };
// Localization
export { authLocalization };
// Types
export type {
AuthUIConfig,
User,
Session,
AuthLocalization,
PathConfig,
AccountPathConfig,
OrganizationPathConfig
};
Install dependencies
pnpm install
Configure environment variables
Copy .env.example to .env and update the values:
cp .env.example .env
Start Mailpit for email testing
Mailpit provides a local SMTP server and web UI for testing emails:
docker compose up -d
Access the Mailpit web UI at http://localhost:8025 to view emails sent during development (magic links, OTP codes, etc.)
Start development server
pnpm dev
# Build library
pnpm run build
# Lint and format
pnpm run lint
pnpm run format
# Stop Mailpit
docker compose down
All authentication emails (magic links, OTP codes) are sent through Mailpit in development:
Better Auth UI for Svelte is built with:
$state, $derived, $effect) for reactive stateThis Svelte port maintains full feature parity with the React version. The API is nearly identical, with these framework-specific differences:
goto functioninvalidateAll() instead of router.refresh()[path] syntax instead of Next.js [path]Contributions are welcome! Please feel free to submit a Pull Request.
MIT