KeyloomKeyloom
Keyloom Auth is currently in beta. Feedback and contributions are welcome!
Ui

UI Components

Complete @keyloom/ui component library reference - authentication forms, organization management, primitives, theming, and customization.

@keyloom/ui

Keyloom's comprehensive UI component library for authentication flows, organization management, and reusable primitives. Built with Tailwind CSS and Radix UI.

Prerequisites

  • React 18+ or Next.js 13+
  • Tailwind CSS 3.4+
  • Keyloom handler configured at /api/auth/[...keyloom]

Installation

pnpm add @keyloom/ui clsx lucide-react tailwindcss @radix-ui/react-dialog @radix-ui/react-dropdown-menu @radix-ui/react-checkbox @radix-ui/react-avatar

Setup

1. Tailwind configuration

tailwind.config.cjs
const keyloom = require("@keyloom/ui/theme/tailwind-preset.cjs");

module.exports = {
  presets: [keyloom],
  content: [
    "./app/**/*.{ts,tsx}",
    "./components/**/*.{ts,tsx}",
    "./node_modules/@keyloom/ui/dist/**/*.{js,ts,jsx,tsx}",
  ],
};

2. CSS variables

app/globals.css
@import "@keyloom/ui/theme/css-vars.css";
@tailwind base;
@tailwind components;
@tailwind utilities;

3. AuthUIProvider (optional)

app/layout.tsx
import { AuthUIProvider } from "@keyloom/ui";

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html>
      <body>
        <AuthUIProvider>{children}</AuthUIProvider>
      </body>
    </html>
  );
}

Authentication Components

SignInForm

Email/password sign-in form with validation and error handling.

app/auth/signin/page.tsx
import { SignInForm } from "@keyloom/ui/auth";
import { Card } from "@keyloom/ui/components/card";

export default function SignInPage() {
  return (
    <div className="min-h-screen grid place-items-center p-6">
      <Card className="w-full max-w-md p-6">
        <h1 className="text-2xl font-semibold mb-6">Welcome back</h1>
        <SignInForm
          redirectTo="/dashboard"
          endpoint="/api/auth/login"
          onSuccess={(res) => console.log("Signed in:", res)}
        />
      </Card>
    </div>
  );
}

Props:

  • redirectTo?: string - Redirect URL after successful sign-in (default: "/")
  • endpoint?: string - API endpoint for sign-in (default: "/api/auth/login")
  • onSuccess?: (res: any) => void - Success callback

MagicLinkForm

Passwordless authentication form that sends magic links via email.

app/auth/magic-link/page.tsx
import { MagicLinkForm } from "@keyloom/ui/auth";
import { Card } from "@keyloom/ui/components/card";

export default function MagicLinkPage() {
  return (
    <div className="min-h-screen grid place-items-center p-6">
      <Card className="w-full max-w-md p-6">
        <h1 className="text-2xl font-semibold mb-6">Sign in with Magic Link</h1>
        <MagicLinkForm
          onSuccess={() => console.log("Magic link sent!")}
          onError={(error) => console.error("Error:", error)}
        />
      </Card>
    </div>
  );
}

Props:

  • onSuccess?: () => void - Success callback when magic link is sent
  • onError?: (error: string) => void - Error callback
  • className?: string - Additional CSS classes

SignUpForm

User registration form with email/password and validation.

app/auth/signup/page.tsx
import { SignUpForm } from "@keyloom/ui/auth";
import { Card } from "@keyloom/ui/components/card";

export default function SignUpPage() {
  return (
    <div className="min-h-screen grid place-items-center p-6">
      <Card className="w-full max-w-md p-6">
        <h1 className="text-2xl font-semibold mb-6">Create account</h1>
        <SignUpForm redirectTo="/dashboard" endpoint="/api/auth/register" />
      </Card>
    </div>
  );
}

Props:

  • redirectTo?: string - Redirect URL after successful sign-up
  • endpoint?: string - API endpoint for registration
  • onSuccess?: (res: any) => void - Success callback

Providers

OAuth provider buttons with automatic provider detection.

components/AuthProviders.tsx
import { Providers } from "@keyloom/ui/auth";

export function AuthProviders() {
  return (
    <Providers
      callbackUrl="/dashboard"
      providers={[
        { id: "github", name: "GitHub" },
        { id: "google", name: "Google" },
        { id: "microsoft", name: "Microsoft" },
      ]}
    />
  );
}

Props:

  • callbackUrl?: string - Redirect URL after OAuth callback
  • providers?: Provider[] - Array of provider configurations

UserButton

User profile dropdown with sign-out functionality.

components/NavBar.tsx
import { UserButton } from "@keyloom/ui/auth";

export function NavBar() {
  return (
    <nav className="flex justify-between items-center p-4">
      <h1>My App</h1>
      <UserButton />
    </nav>
  );
}

ForgotPasswordForm

Password reset request form for users who forgot their password.

app/auth/forgot-password/page.tsx
import { ForgotPasswordForm } from "@keyloom/ui/auth";
import { Card } from "@keyloom/ui/components/card";

export default function ForgotPasswordPage() {
  return (
    <div className="min-h-screen grid place-items-center p-6">
      <Card className="w-full max-w-md p-6">
        <h1 className="text-2xl font-semibold mb-6">Reset your password</h1>
        <ForgotPasswordForm endpoint="/api/auth/forgot-password" />
      </Card>
    </div>
  );
}

ResetPasswordForm

Password reset form for users with a valid reset token.

app/auth/reset-password/page.tsx
import { ResetPasswordForm } from "@keyloom/ui/auth";
import { Card } from "@keyloom/ui/components/card";

export default function ResetPasswordPage() {
  return (
    <div className="min-h-screen grid place-items-center p-6">
      <Card className="w-full max-w-md p-6">
        <h1 className="text-2xl font-semibold mb-6">Set new password</h1>
        <ResetPasswordForm endpoint="/api/auth/reset-password" />
      </Card>
    </div>
  );
}

EmailVerificationForm

Email verification form for confirming user email addresses.

app/auth/verify-email/page.tsx
import { EmailVerificationForm } from "@keyloom/ui/auth";
import { Card } from "@keyloom/ui/components/card";

export default function VerifyEmailPage() {
  return (
    <div className="min-h-screen grid place-items-center p-6">
      <Card className="w-full max-w-md p-6">
        <h1 className="text-2xl font-semibold mb-6">Verify your email</h1>
        <EmailVerificationForm endpoint="/api/auth/verify-email" />
      </Card>
    </div>
  );
}

AuthLayout Components

Pre-built layout components for authentication pages with consistent styling.

app/(auth)/layout.tsx
import { AuthLayout } from "@keyloom/ui/auth";

export default function Layout({ children }: { children: React.ReactNode }) {
  return (
    <AuthLayout
      title="Welcome to My App"
      subtitle="Secure authentication platform"
    >
      {children}
    </AuthLayout>
  );
}

Available Layout Components:

  • AuthLayout - General authentication layout
  • SignInLayout - Optimized for sign-in pages
  • SignUpLayout - Optimized for registration pages
  • ForgotPasswordLayout - For password reset flows
  • ResetPasswordLayout - For password reset forms
  • VerifyEmailLayout - For email verification

RedirectToSignIn & Authentication Guards

Components and utilities for protecting routes and redirecting unauthenticated users.

components/ProtectedPage.tsx
import { RedirectToSignIn, withAuth } from "@keyloom/ui/auth";
import { useSession } from "@keyloom/react";

function ProtectedContent() {
  return <div>This content is protected</div>;
}

// Option 1: Using RedirectToSignIn component
export function ProtectedPage() {
  const { data: session, status } = useSession();

  if (status === "loading") return <div>Loading...</div>;
  if (!session) return <RedirectToSignIn />;

  return <ProtectedContent />;
}

// Option 2: Using withAuth HOC
export default withAuth(ProtectedContent);

// Option 3: Using useRequireAuth hook
import { useRequireAuth } from "@keyloom/ui/auth";

export function AnotherProtectedPage() {
  useRequireAuth(); // Automatically redirects if not authenticated

  return <ProtectedContent />;
}

Organization Components

OrganizationSwitcher

Organization switcher dropdown for multi-tenant applications.

components/OrganizationSwitcher.tsx
import { OrganizationSwitcher } from "@keyloom/ui/auth";

export function AppHeader() {
  return (
    <header className="flex justify-between items-center p-4">
      <h1>My App</h1>
      <div className="flex items-center gap-4">
        <OrganizationSwitcher />
        <UserButton />
      </div>
    </header>
  );
}

MembersTable

Data table for organization member management with roles and actions.

app/org/members/page.tsx
import { MembersTable } from "@keyloom/ui/org";

export default function MembersPage() {
  return (
    <div className="p-6">
      <h1 className="text-2xl font-semibold mb-6">Team Members</h1>
      <MembersTable
        orgId="org_123"
        onInvite={() => console.log("Invite member")}
        onRemove={(memberId) => console.log("Remove:", memberId)}
      />
    </div>
  );
}

CreateOrgDialog

Modal dialog for creating new organizations.

components/CreateOrgButton.tsx
import { CreateOrgDialog } from "@keyloom/ui/org";
import { Button } from "@keyloom/ui/components/button";

export function CreateOrgButton() {
  return (
    <CreateOrgDialog
      trigger={<Button>Create Organization</Button>}
      onSuccess={(org) => console.log("Created:", org)}
    />
  );
}

InviteMemberDialog

Modal dialog for inviting new team members.

components/InviteMemberButton.tsx
import { InviteMemberDialog } from "@keyloom/ui/org";
import { Button } from "@keyloom/ui/components/button";

export function InviteMemberButton() {
  return (
    <InviteMemberDialog
      trigger={<Button>Invite Member</Button>}
      orgId="org_123"
      onSuccess={(invitation) => console.log("Invited:", invitation)}
    />
  );
}

OrganizationView

Complete organization management interface with settings and member overview.

app/org/[id]/page.tsx
import { OrganizationView } from "@keyloom/ui/auth";

export default function OrganizationPage({ params }: { params: { id: string } }) {
  return (
    <OrganizationView
      organizationId={params.id}
      onUpdateSettings={(settings) => console.log("Updated:", settings)}
      onManageMembers={() => console.log("Manage members")}
    />
  );
}

OrganizationMemberCard

Individual member card component for displaying member information and actions.

components/MemberCard.tsx
import { OrganizationMemberCard } from "@keyloom/ui/auth";

export function MemberCard({ member }: { member: OrganizationMember }) {
  return (
    <OrganizationMemberCard
      member={member}
      onUpdateRole={(role) => console.log("Update role:", role)}
      onRemove={() => console.log("Remove member")}
      showActions={true}
    />
  );
}

Account Management Components

AccountView

Complete account management interface with profile editing and settings.

app/account/page.tsx
import { AccountView } from "@keyloom/ui/auth";

export default function AccountPage() {
  return (
    <AccountView
      onUpdateProfile={(profile) => console.log("Updated:", profile)}
      onDeleteAccount={() => console.log("Delete account")}
      showDangerZone={true}
    />
  );
}

AccountSettingsCard

Account settings management card with form controls.

components/AccountSettings.tsx
import { AccountSettingsCard } from "@keyloom/ui/auth";

export function AccountSettings() {
  return (
    <AccountSettingsCard
      onUpdateEmail={(email) => console.log("Update email:", email)}
      onUpdatePassword={(password) => console.log("Update password")}
      onUpdateProfile={(profile) => console.log("Update profile:", profile)}
    />
  );
}

SecuritySettingsCard

Security settings management including password, 2FA, and session management.

components/SecuritySettings.tsx
import { SecuritySettingsCard } from "@keyloom/ui/auth";

export function SecuritySettings() {
  return (
    <SecuritySettingsCard
      onChangePassword={() => console.log("Change password")}
      onEnable2FA={() => console.log("Enable 2FA")}
      onManageSessions={() => console.log("Manage sessions")}
    />
  );
}

ApiKeysCard

API key management interface for developers and integrations.

components/ApiKeys.tsx
import { ApiKeysCard } from "@keyloom/ui/auth";

export function ApiKeys() {
  return (
    <ApiKeysCard
      onCreateKey={(name) => console.log("Create key:", name)}
      onRevokeKey={(keyId) => console.log("Revoke key:", keyId)}
      onRegenerateKey={(keyId) => console.log("Regenerate key:", keyId)}
    />
  );
}

User Profile Components

UserProfile

Complete user profile display with avatar, information, and actions.

components/UserProfile.tsx
import { UserProfile } from "@keyloom/ui/auth";

export function ProfilePage() {
  return (
    <UserProfile
      user={user}
      onEdit={() => console.log("Edit profile")}
      onChangeAvatar={(file) => console.log("Change avatar:", file)}
      showEditButton={true}
    />
  );
}

UserProfileCompact

Compact user profile variant for sidebars and small spaces.

components/Sidebar.tsx
import { UserProfileCompact } from "@keyloom/ui/auth";

export function Sidebar() {
  return (
    <div className="w-64 bg-background border-r">
      <UserProfileCompact
        user={user}
        showStatus={true}
        onClick={() => console.log("Profile clicked")}
      />
    </div>
  );
}

RBAC Components

RoleGate

Conditional rendering based on user roles and permissions.

components/AdminPanel.tsx
import { RoleGate } from "@keyloom/ui/rbac";

export function AdminPanel() {
  return (
    <div>
      <h2>Dashboard</h2>

      <RoleGate roles={["admin", "owner"]}>
        <div className="bg-red-50 p-4 rounded">
          <h3>Admin Only Content</h3>
          <p>This is only visible to admins and owners.</p>
        </div>
      </RoleGate>

      <RoleGate permissions={["users:write"]}>
        <button>Manage Users</button>
      </RoleGate>
    </div>
  );
}

Props:

  • roles?: string[] - Required roles for access
  • permissions?: string[] - Required permissions for access
  • fallback?: React.ReactNode - Content to show when access denied

Primitive Components

Button

Styled button component with variants and sizes.

components/ActionButtons.tsx
import { Button } from "@keyloom/ui/components/button";

export function ActionButtons() {
  return (
    <div className="flex gap-2">
      <Button variant="primary" size="md">
        Primary Action
      </Button>
      <Button variant="outline" size="md">
        Secondary
      </Button>
      <Button variant="danger" size="sm">
        Delete
      </Button>
    </div>
  );
}

Props:

  • variant?: 'default' | 'primary' | 'ghost' | 'outline' | 'danger'
  • size?: 'sm' | 'md' | 'lg'

Card

Container component with consistent styling.

components/StatsCard.tsx
import { Card } from "@keyloom/ui/components/card";

export function StatsCard({ title, value }: { title: string; value: string }) {
  return (
    <Card className="p-6">
      <h3 className="text-sm font-medium text-muted-foreground">{title}</h3>
      <p className="text-2xl font-bold">{value}</p>
    </Card>
  );
}

Input

Form input component with validation states.

components/ContactForm.tsx
import { Input } from "@keyloom/ui/components/input";
import { Label } from "@keyloom/ui/components/label";

export function ContactForm() {
  return (
    <form className="space-y-4">
      <div>
        <Label htmlFor="email">Email</Label>
        <Input
          id="email"
          type="email"
          placeholder="Enter your email"
          required
        />
      </div>
      <div>
        <Label htmlFor="message">Message</Label>
        <Input id="message" as="textarea" placeholder="Your message" rows={4} />
      </div>
    </form>
  );
}

Dialog

Modal dialog component built on Radix UI.

components/ConfirmDialog.tsx
import {
  Dialog,
  DialogContent,
  DialogHeader,
  DialogTitle,
  DialogTrigger,
} from "@keyloom/ui/components/dialog";
import { Button } from "@keyloom/ui/components/button";

export function ConfirmDialog({ onConfirm }: { onConfirm: () => void }) {
  return (
    <Dialog>
      <DialogTrigger asChild>
        <Button variant="danger">Delete Account</Button>
      </DialogTrigger>
      <DialogContent>
        <DialogHeader>
          <DialogTitle>Are you sure?</DialogTitle>
        </DialogHeader>
        <p>This action cannot be undone.</p>
        <div className="flex justify-end gap-2 mt-4">
          <Button variant="outline">Cancel</Button>
          <Button variant="danger" onClick={onConfirm}>
            Delete
          </Button>
        </div>
      </DialogContent>
    </Dialog>
  );
}

Theming and Customization

Design Tokens

Access design tokens programmatically:

components/CustomComponent.tsx
import { theme } from "@keyloom/ui";

export function CustomComponent() {
  return (
    <div
      style={{
        backgroundColor: theme.colors.primary[500],
        borderRadius: theme.radii.md,
        padding: theme.spacing[4],
      }}
    >
      Custom styled component
    </div>
  );
}

CSS Variables

Use CSS variables for dynamic theming:

components/custom.module.css
.custom-button {
  background-color: var(--kl-primary);
  color: var(--kl-primary-fg);
  border-radius: var(--kl-radius);
  box-shadow: var(--kl-shadow-sm);
}

.dark .custom-button {
  /* Dark mode automatically handled by CSS variables */
}

Tailwind Classes

Use Keyloom's Tailwind preset classes:

components/ThemedCard.tsx
export function ThemedCard() {
  return (
    <div className="bg-background text-foreground border border-border rounded-lg p-6 shadow-sm">
      <h3 className="text-primary font-semibold">Themed Card</h3>
      <p className="text-muted-foreground">Using Keyloom design tokens</p>
    </div>
  );
}

Dark Mode

Dark mode is handled automatically via CSS variables:

components/ThemeToggle.tsx
"use client";

import { useEffect, useState } from "react";
import { Button } from "@keyloom/ui/components/button";

export function ThemeToggle() {
  const [isDark, setIsDark] = useState(false);

  useEffect(() => {
    const isDarkMode = document.documentElement.classList.contains("dark");
    setIsDark(isDarkMode);
  }, []);

  const toggleTheme = () => {
    document.documentElement.classList.toggle("dark");
    setIsDark(!isDark);
  };

  return (
    <Button variant="ghost" onClick={toggleTheme}>
      {isDark ? "☀️" : "🌙"}
    </Button>
  );
}

Advanced Usage

Complete Authentication Flow

app/auth/page.tsx
import { SignInForm, SignUpForm, Providers } from "@keyloom/ui/auth";
import { Card } from "@keyloom/ui/components/card";
import { Button } from "@keyloom/ui/components/button";
import { useState } from "react";

export default function AuthPage() {
  const [mode, setMode] = useState<"signin" | "signup">("signin");

  return (
    <div className="min-h-screen grid place-items-center p-6">
      <Card className="w-full max-w-md p-6 space-y-6">
        <div className="text-center">
          <h1 className="text-2xl font-semibold">
            {mode === "signin" ? "Welcome back" : "Create account"}
          </h1>
        </div>

        <Providers callbackUrl="/dashboard" />

        <div className="relative">
          <div className="absolute inset-0 flex items-center">
            <span className="w-full border-t" />
          </div>
          <div className="relative flex justify-center text-xs uppercase">
            <span className="bg-background px-2 text-muted-foreground">
              Or continue with
            </span>
          </div>
        </div>

        {mode === "signin" ? (
          <SignInForm redirectTo="/dashboard" />
        ) : (
          <SignUpForm redirectTo="/dashboard" />
        )}

        <div className="text-center">
          <Button
            variant="ghost"
            onClick={() => setMode(mode === "signin" ? "signup" : "signin")}
          >
            {mode === "signin"
              ? "Don't have an account? Sign up"
              : "Already have an account? Sign in"}
          </Button>
        </div>
      </Card>
    </div>
  );
}

Organization Dashboard

app/org/dashboard/page.tsx
import { OrganizationSwitcher, MembersTable, CreateOrgDialog } from "@keyloom/ui/auth";
import { RoleGate } from "@keyloom/ui/rbac";
import { UserButton } from "@keyloom/ui/auth";
import { Card } from "@keyloom/ui/components/card";
import { Button } from "@keyloom/ui/components/button";

export default function OrgDashboard() {
  return (
    <div className="min-h-screen bg-muted/50">
      <header className="bg-background border-b px-6 py-4">
        <div className="flex justify-between items-center">
          <div className="flex items-center gap-4">
            <h1 className="text-xl font-semibold">Dashboard</h1>
            <OrganizationSwitcher />
          </div>
          <div className="flex items-center gap-2">
            <RoleGate roles={["owner", "admin"]}>
              <CreateOrgDialog
                trigger={<Button size="sm">New Organization</Button>}
              />
            </RoleGate>
            <UserButton />
          </div>
        </div>
      </header>

      <main className="p-6">
        <div className="grid gap-6">
          <Card className="p-6">
            <h2 className="text-lg font-semibold mb-4">Team Members</h2>
            <MembersTable orgId="current-org" />
          </Card>
        </div>
      </main>
    </div>
  );
}

Performance Considerations

  • Tree Shaking: Import only the components you need
  • Bundle Size: Core components are lightweight (~15KB gzipped)
  • SSR Compatible: All components work with Next.js SSR/SSG
  • Lazy Loading: Use dynamic imports for large components

Accessibility

  • ARIA Labels: All interactive components include proper ARIA attributes
  • Keyboard Navigation: Full keyboard support for all components
  • Screen Readers: Semantic HTML and proper labeling
  • Focus Management: Logical focus order and visible focus indicators

Troubleshooting

Styles not applying

  • Ensure Tailwind preset is configured correctly
  • Import CSS variables in your global CSS file
  • Check content paths include @keyloom/ui dist files

Components not rendering

  • Verify all peer dependencies are installed
  • Check React version compatibility (18+)
  • Ensure proper import paths

TypeScript errors

  • Update to latest TypeScript version (5.0+)
  • Check component prop types match expected interface
  • Verify proper generic type usage

See also

Next steps

  • Set up Tailwind configuration with Keyloom preset
  • Import CSS variables and configure theming
  • Build your authentication flow with provided components
  • Customize components to match your brand

How is this guide?