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

@withwiz/auth-ui

v0.5.0

Published

Customizable authentication UI components for withwiz projects

Readme

@withwiz/auth-ui

Customizable authentication UI components for React / Next.js projects.

Features

  • Login, Signup, Forgot Password, Reset Password, Email Verification forms
  • OAuth support (Google, GitHub, Kakao)
  • i18n (Korean, English, Japanese)
  • Slot-based layout customization
  • Lifecycle hooks (onBeforeSubmit, onSuccess, onError)
  • AuthProvider context with session management
  • Zod-based form validation
  • Unstyled mode for full CSS control
  • CSS custom properties for theming

Structure

The package is layered. Form is the real unit — a self-contained card (header + validation + API calls). A Page is just a thin wrapper: AuthLayout (2-column split + decorative panel) around the matching Form.

Pick your entry point (granularity):

  〔Screen / Page〕  whole thing     ← built-in split layout, no assembly
     LoginScreen / SignupScreen / ...   (Screen = Form-shaped name)
     LoginPage   / SignupPage   / ...   (Page   = route-component name)
     │  same composition, two names
     └─ both are just a wrap:
          ┌─────────────────────────────┐
          │  AuthLayout (2-col split,    │
          │   triangle panel, logo)      │
          │   └── <LoginForm {...props}/>│  ← the core
          └─────────────────────────────┘

  〔Form〕  core only   ★recommended for custom layouts★
     LoginForm / SignupForm / ForgotPasswordForm /
     ResetPasswordForm / EmailVerificationForm
     │  (own card max-384 + header + zod validation + API calls)
     │
     └─ your layout, your call:
          <Modal>          <Sidebar>        <CustomPage>
            └ <LoginForm/>    └ <SignupForm/>  └ <LoginForm/>

  〔Parts〕  finer grain
     OAuthButtons   AuthLayout   useAuth / AuthProvider
// 1) Page — built-in split layout, zero assembly
import { LoginPage } from '@withwiz/auth-ui';
<LoginPage providers={['google', 'kakao']} />

// 2) Form only — your own layout (recommended)
import { LoginForm } from '@withwiz/auth-ui';
<MyModal>
  <LoginForm providers={['google', 'kakao']} redirectAfterLogin="/dashboard" />
</MyModal>

// 3) Form + your own wrap
import { LoginForm, AuthLayout } from '@withwiz/auth-ui';
<AuthLayout pattern="hexagon" logo={<Logo />}>
  <LoginForm providers={['google']} />
</AuthLayout>

Form components render their own card and header. logo and the decorative panel come from AuthLayout/Page only — a standalone Form shows neither.

Installation

pnpm add @withwiz/auth-ui

Peer dependencies:

pnpm add react react-dom next

Quick Start

import { AuthProvider, LoginForm } from '@withwiz/auth-ui';
import '@withwiz/auth-ui/styles';

export default function Login() {
  return (
    <AuthProvider apiBasePath="/api/auth">
      {/* Form only — drop into your own layout */}
      <LoginForm
        providers={['google', 'github']}
        locale="en"
        redirectAfterLogin="/dashboard"
      />
    </AuthProvider>
  );
}

Pages

A Page = AuthLayout + the matching Form. Use it when you want the built-in split layout without assembling it yourself. Page props are the union of AuthLayout's visual props (logo, pattern, backgroundColor, leftPanel) and the wrapped Form's props — extra props pass straight through to the form.

import { LoginPage } from '@withwiz/auth-ui';
import '@withwiz/auth-ui/styles';

<LoginPage
  logo={<img src="/logo.svg" alt="Logo" />}
  pattern="triangle"             // 'triangle' | 'hexagon' | 'dots' | 'none'
  backgroundColor="#f0f4ff"
  providers={['google', 'kakao']}   // ↓ forwarded to <LoginForm />
  redirectAfterLogin="/dashboard"
  locale="ko"
/>

Available: LoginPage, SignupPage, ForgotPasswordPage, ResetPasswordPage, EmailVerificationPage (also exported from @withwiz/auth-ui/pages).

Screens

A Screen is the bundled experience used like a single form: the matching Form + the triangle side panel + an optional logo, all in one component. Same composition as a Page, just expressed as a Form-shaped default — reach for it when you want "the login form, with our layout" in one import.

import { LoginScreen } from '@withwiz/auth-ui';
import '@withwiz/auth-ui/styles';

// Triangle panel is on by default; logo is optional (renders nothing if omitted).
<LoginScreen
  logo={<Logo />}                  // optional
  providers={['google', 'kakao']}  // ↓ forwarded to <LoginForm />
  redirectAfterLogin="/dashboard"
  locale="ko"
/>

Bake your brand in once with a thin app-side wrapper so every screen shares it:

// app/auth.tsx
import { LoginScreen, SignupScreen, ForgotPasswordScreen, ResetPasswordScreen } from '@withwiz/auth-ui';
import { Logo } from '@/components/Logo';

const brand = { logo: <Logo />, pattern: 'triangle', backgroundColor: '#f0f4ff' } as const;

export const Login  = (p) => <LoginScreen  {...brand} {...p} />;
export const Signup = (p) => <SignupScreen {...brand} {...p} />;
export const Forgot = (p) => <ForgotPasswordScreen {...brand} {...p} />;
export const Reset  = (p) => <ResetPasswordScreen  {...brand} {...p} />;

Available: LoginScreen, SignupScreen, ForgotPasswordScreen, ResetPasswordScreen, EmailVerificationScreen (also exported from @withwiz/auth-ui/screens). Each *ScreenProps equals the matching *PageProps.

Screen and Page render the same thing today. Screen is the Form-shaped name; Page is kept for the route-component convention.

Components

AuthProvider

Wraps your app to provide authentication context (useAuth hook).

<AuthProvider
  apiBasePath="/api/auth"        // API endpoint base path (default: '/api/auth')
  onAuthChange={(isAuth) => {}}  // Called when auth state changes
>
  {children}
</AuthProvider>

useAuth Hook

const { isAuthenticated, isLoading, user, login, logout, refresh } = useAuth();

| Property | Type | Description | |---|---|---| | isAuthenticated | boolean | Whether user is logged in | | isLoading | boolean | Loading state during initial check | | user | { id, email, name?, role } | Current user or null | | login(email, password) | Promise<void> | Login method | | logout() | Promise<void> | Logout method | | refresh() | Promise<boolean> | Refresh token |


LoginForm

<LoginForm
  providers={['google', 'github', 'kakao']}
  locale="en"                    // 'ko' | 'en' | 'ja'
  redirectAfterLogin="/dashboard"
  showMagicLink={false}
  showForgotPassword={true}
  showSignupLink={true}
  unstyled={false}
  onOAuthClick={(provider) => {}}  // Override default OAuth redirect
  apiBasePath="/api/auth"
  title="Welcome back"
  subtitle="Sign in to your account"
  messages={{ submitButton: 'Log In' }}  // Partial override
  hooks={{
    onBeforeSubmit: async (data) => true,
    onSuccess: (user) => console.log(user),
    onError: (error) => console.error(error),
  }}
  slots={{
    header: <MyCustomHeader />,
    footer: <MyCustomFooter />,
    oauthSection: <MyOAuth />,
    beforeForm: <Banner />,
    afterForm: <Links />,
  }}
/>

SignupForm

<SignupForm
  providers={['google']}
  locale="ko"
  redirectAfterSignup="/welcome"
  showLoginLink={true}
  unstyled={false}
  apiBasePath="/api/auth"
  onOAuthClick={(provider) => {}}
  extraFields={[
    { name: 'company', label: 'Company', required: true, placeholder: 'Acme Inc.' },
  ]}
  hooks={{
    onBeforeSubmit: async (data) => true,
    onSuccess: (user) => {},
    onError: (error) => {},
  }}
  slots={{ header: null, footer: null }}
/>

ForgotPasswordForm

<ForgotPasswordForm
  locale="en"
  apiBasePath="/api/auth"
  loginUrl="/login"
  messages={{ title: 'Reset Password' }}
/>

ResetPasswordForm

<ResetPasswordForm
  token={tokenFromUrl}            // required — from the reset email link
  locale="ko"
  apiBasePath="/api/auth"
  loginUrl="/login"
  messages={{ title: '비밀번호 재설정' }}
/>

EmailVerificationForm

<EmailVerificationForm
  token={tokenFromUrl}            // required — from the verification email link
  locale="ko"
  apiBasePath="/api/auth"
  loginUrl="/login"
  resendUrl="/auth/resend"
/>

AuthLayout

Split-panel layout with decorative patterns for desktop. This is the layout the *Screen/*Page components wrap around their form — use it directly when you want that split panel + logo but need to control placement yourself.

import { AuthLayout, LoginForm } from '@withwiz/auth-ui';

<AuthLayout
  logo={<img src="/logo.svg" alt="Logo" />}
  title="Welcome"
  subtitle="Sign in to continue"
  pattern="triangle"             // 'triangle' | 'hexagon' | 'dots' | 'none'
  backgroundColor="#f0f4ff"
  leftPanel={<CustomPanel />}    // Override the decorative panel
  fullHeight={true}              // false → drop min-height:100vh to embed in a shell
>
  <LoginForm providers={['google']} />
</AuthLayout>

Embedding the split layout inside your own shell. AuthLayout (and therefore *Screen/*Page) defaults to min-height: 100vh — it expects to own the viewport, which is right for a dedicated auth route. To drop the same split panel + logo into a bounded region of your own page chrome, pass fullHeight={false} so it fills its container instead of the screen:

// inside your app shell, in a sized region
<section style={{ height: 600 }}>
  <AuthLayout pattern="triangle" logo={<Logo />} fullHeight={false}>
    <LoginForm providers={['google']} apiBasePath="/api/auth" />
  </AuthLayout>
</section>

For a plain embed with no decorative panel at all, use the bare LoginForm (just the card) instead.


OAuthButtons

Standalone OAuth button group.

<OAuthButtons
  providers={['google', 'github', 'kakao']}
  mode="login"                   // 'login' | 'signup'
  onOAuthStart={(provider) => {}}
  onOAuthClick={(provider) => {}}  // Override default redirect
  disabled={false}
  apiBasePath="/api/auth"
/>

Hooks

useAuthForm

Generic form hook with Zod validation.

import { useAuthForm } from '@withwiz/auth-ui/hooks';
import { z } from 'zod';

const schema = z.object({
  email: z.string().email(),
  password: z.string().min(8),
});

function MyForm() {
  const { errors, loading, serverError, submit } = useAuthForm({
    schema,
    onSubmit: async (data) => {
      await fetch('/api/auth/login', { method: 'POST', body: JSON.stringify(data) });
    },
  });

  return (
    <form onSubmit={(e) => { e.preventDefault(); submit({ email, password }); }}>
      {errors.email && <span>{errors.email}</span>}
      {serverError && <span>{serverError}</span>}
      <button disabled={loading}>Submit</button>
    </form>
  );
}

i18n

Built-in locales: ko, en, ja.

import { getMessages, mergeMessages } from '@withwiz/auth-ui/i18n';

// Get all messages for a locale
const messages = getMessages('en');

// Merge with custom overrides
const custom = mergeMessages('en', {
  login: { title: 'Sign In', submitButton: 'Go' },
});

Each component accepts a messages prop for partial overrides without replacing the full locale.

Styling

Default styles

Import the built-in stylesheet:

import '@withwiz/auth-ui/styles';

CSS Custom Properties

Override via CSS variables:

.wiz-auth-page {
  --wiz-auth-primary: #4f46e5;
  --wiz-auth-primary-hover: #4338ca;
  --wiz-auth-error: #dc2626;
  --wiz-auth-background: #ffffff;
  --wiz-auth-foreground: #111827;
  --wiz-auth-border: #d1d5db;
  --wiz-auth-radius: 6px;
  --wiz-auth-font: system-ui, -apple-system, sans-serif;
}

Unstyled Mode

Pass unstyled={true} to remove all default inline styles and class names, giving you full control.

<LoginForm unstyled className="my-login-form" />

API Endpoints (Expected)

The components expect these server-side endpoints:

| Method | Endpoint | Description | |---|---|---| | POST | /api/auth/login | Email/password login | | POST | /api/auth/signup | User registration | | POST | /api/auth/logout | Logout | | POST | /api/auth/refresh | Token refresh | | GET | /api/auth/me | Get current user | | POST | /api/auth/forgot-password | Send reset email | | POST | /api/auth/reset-password | Set new password ({ token, password }) | | POST | /api/auth/verify-email | Verify email ({ token }) | | POST | /api/auth/oauth/login | Start OAuth flow (returns { loginUrl }) |

All endpoints are prefixed with apiBasePath (default: /api/auth).

Development

pnpm install
pnpm build          # Build library
pnpm test           # Run tests
pnpm test:watch     # Watch mode
pnpm test:coverage  # Coverage report

License

MIT