| name | neon-auth-react |
| description | Sets up Neon Auth in React applications (Vite, CRA). Configures authentication adapters, creates auth client, and sets up UI components. Use when adding auth-only to React apps (no database needed). |
| allowed-tools | ["Bash","Write","Read","Edit","Glob","Grep"] |
Neon Auth for React
Help developers set up @neondatabase/auth (authentication only, no database) in React applications with Vite, Create React App, or similar bundlers.
When to Use
Use this skill when:
- Setting up auth-only in React (no database needed)
- User already has a database solution
- User mentions "@neondatabase/auth" without "neon-js"
- User is NOT using Next.js (use
neon-auth-nextjs skill for Next.js)
Critical Rules
- Adapter Factory Pattern: Always call adapters with
() - they are factory functions
- React Adapter Import: Use subpath
@neondatabase/auth/react/adapters
- createAuthClient takes URL as first arg:
createAuthClient(url, config)
- CSS Import: Choose ONE - either
/ui/css OR /ui/tailwind, never both
Setup
1. Install
npm install @neondatabase/auth
2. Create Client (src/auth-client.ts)
import { createAuthClient } from '@neondatabase/auth';
import { BetterAuthReactAdapter } from '@neondatabase/auth/react/adapters';
export const authClient = createAuthClient(
import.meta.env.VITE_AUTH_URL,
{
adapter: BetterAuthReactAdapter(),
}
);
3. Create Provider (src/providers.tsx)
import { NeonAuthUIProvider } from '@neondatabase/auth/react/ui';
import { useNavigate } from 'react-router-dom';
import { Link } from 'react-router-dom';
import { authClient } from './auth-client';
import '@neondatabase/auth/ui/css';
export function Providers({ children }: { children: React.ReactNode }) {
const navigate = useNavigate();
return (
<NeonAuthUIProvider
authClient={authClient}
navigate={navigate}
redirectTo="/dashboard"
Link={({ children, href }) => <Link to={href}>{children}</Link>}
>
{children}
</NeonAuthUIProvider>
);
}
4. Wrap App (src/main.tsx)
import { BrowserRouter } from 'react-router-dom';
import { Providers } from './providers';
import App from './App';
ReactDOM.createRoot(document.getElementById('root')!).render(
<BrowserRouter>
<Providers>
<App />
</Providers>
</BrowserRouter>
);
CSS & Styling
Import Options
Without Tailwind (pre-built CSS bundle ~47KB):
@import '@neondatabase/auth/ui/css';
With Tailwind CSS v4:
@import 'tailwindcss';
@import '@neondatabase/auth/ui/tailwind';
IMPORTANT: Never import both - causes duplicate styles.
Dark Mode
The provider includes next-themes for dark mode. Control via defaultTheme prop:
<NeonAuthUIProvider
authClient={authClient}
defaultTheme="system"
>
Custom Theming
Override CSS variables in your stylesheet:
:root {
--primary: oklch(0.7 0.15 250);
--primary-foreground: oklch(0.98 0 0);
--background: oklch(1 0 0);
--foreground: oklch(0.1 0 0);
--card: oklch(1 0 0);
--card-foreground: oklch(0.1 0 0);
--border: oklch(0.9 0 0);
--input: oklch(0.9 0 0);
--ring: oklch(0.7 0 0);
--radius: 0.5rem;
}
.dark {
--background: oklch(0.15 0 0);
--foreground: oklch(0.98 0 0);
}
NeonAuthUIProvider Props
Full configuration options:
<NeonAuthUIProvider
authClient={authClient}
navigate={navigate}
Link={({href, children}) => <Link to={href}>{children}</Link>}
redirectTo="/dashboard"
social={{
providers: ['google'],
}}
emailOTP={true}
emailVerification={true}
magicLink={false}
multiSession={false}
credentials={{
forgotPassword: true,
}}
signUp={{
fields: ['name'],
}}
account={{
fields: ['image', 'name', 'company', 'age', 'newsletter'],
}}
avatar={{
size: 256,
extension: 'webp',
}}
organization={{}}
defaultTheme="system"
localization={{
SIGN_IN: 'Welcome Back',
SIGN_IN_DESCRIPTION: 'Sign in to your account',
SIGN_UP: 'Create Account',
SIGN_UP_DESCRIPTION: 'Join us today',
FORGOT_PASSWORD: 'Forgot Password?',
OR_CONTINUE_WITH: 'or continue with',
}}
>
{children}
</NeonAuthUIProvider>
UI Components
AuthView - Main Auth Interface
Handles sign-in, sign-up, forgot password, and callback routes:
import { AuthView } from '@neondatabase/auth/react/ui';
function AuthPage() {
const { pathname } = useParams();
return <AuthView pathname={pathname} />;
}
Supported pathnames: sign-in, sign-up, forgot-password, reset-password, callback, sign-out
Conditional Rendering
import {
SignedIn,
SignedOut,
AuthLoading,
RedirectToSignIn
} from '@neondatabase/auth/react/ui';
function MyPage() {
return (
<>
{/* Show while checking auth state */}
<AuthLoading>
<LoadingSpinner />
</AuthLoading>
{/* Show only when authenticated */}
<SignedIn>
<Dashboard />
</SignedIn>
{/* Show only when NOT authenticated */}
<SignedOut>
<LandingPage />
</SignedOut>
{/* Redirect to sign-in if not authenticated */}
<RedirectToSignIn />
</>
);
}
UserButton
Dropdown menu with user avatar, name, and sign-out:
import { UserButton } from '@neondatabase/auth/react/ui';
function Header() {
return (
<header>
<nav>...</nav>
<UserButton />
</header>
);
}
Account Management Components
import {
AccountSettingsCards,
SecuritySettingsCards,
SessionsCard,
ChangePasswordCard,
ChangeEmailCard,
DeleteAccountCard,
ProvidersCard,
} from '@neondatabase/auth/react/ui';
function AccountPage() {
const { view } = useParams();
return (
<>
<RedirectToSignIn />
<SignedIn>
{view === 'settings' && <AccountSettingsCards />}
{view === 'security' && (
<>
<ChangePasswordCard />
<SecuritySettingsCards />
</>
)}
{view === 'sessions' && <SessionsCard />}
</SignedIn>
</>
);
}
Organization Components
import {
OrganizationSwitcher,
OrganizationSettingsCards,
OrganizationMembersCard,
AcceptInvitationCard,
} from '@neondatabase/auth/react/ui';
Adapter Options
BetterAuthReactAdapter (Recommended for React)
Native Better Auth API with React hooks:
import { BetterAuthReactAdapter } from '@neondatabase/auth/react/adapters';
const authClient = createAuthClient(url, {
adapter: BetterAuthReactAdapter(),
});
await authClient.signIn.email({ email, password });
await authClient.signUp.email({ email, password, name });
await authClient.signIn.social({ provider: 'google', callbackURL: '/dashboard' });
await authClient.signOut();
const session = await authClient.getSession();
const { data, isPending, error } = authClient.useSession();
SupabaseAuthAdapter (Supabase-compatible API)
For migrating from Supabase or familiar API:
import { SupabaseAuthAdapter } from '@neondatabase/auth/vanilla/adapters';
const authClient = createAuthClient(url, {
adapter: SupabaseAuthAdapter(),
});
await authClient.signUp({ email, password, options: { data: { name } } });
await authClient.signInWithPassword({ email, password });
await authClient.signInWithOAuth({ provider: 'google', options: { redirectTo } });
await authClient.signOut();
const { data: session } = await authClient.getSession();
authClient.onAuthStateChange((event, session) => {
console.log(event);
});
BetterAuthVanillaAdapter (Non-React)
For vanilla JS/TS without React hooks:
import { BetterAuthVanillaAdapter } from '@neondatabase/auth/vanilla/adapters';
const authClient = createAuthClient(url, {
adapter: BetterAuthVanillaAdapter(),
});
Social/OAuth Providers
Configuration
Enable providers in NeonAuthUIProvider:
<NeonAuthUIProvider
social={{
providers: ['google'],
}}
>
Programmatic OAuth Sign-In
await authClient.signIn.social({
provider: 'google',
callbackURL: '/dashboard',
scopes: ['email', 'profile'],
});
await authClient.signInWithOAuth({
provider: 'google',
options: {
redirectTo: '/dashboard',
scopes: 'email profile',
},
});
Supported Providers
google, github, twitter, discord, apple, microsoft, facebook, linkedin, spotify, twitch, gitlab, bitbucket
OAuth in Iframes
OAuth automatically uses popup flow when running in iframes (due to X-Frame-Options restrictions). No configuration needed.
Session Hook