Skip to content

RegisterForm Component ​

Complete registration and login form with tab navigation, validation, and Supabase integration. Features modern UI with dark mode support and comprehensive form handling.

Overview ​

The RegisterForm component provides a dual-purpose authentication interface with signup and login functionality in a single component. It includes form validation, error handling, loading states, and seamless integration with Supabase authentication.

Preview ​

Register Form Component Preview

Basic Usage ​

vue
<template>
  <div class="auth-page">
    <RegisterForm 
      title="Get Started"
      subtitle="Create your account to begin"
    />
  </div>
</template>

<script setup lang="ts">
import RegisterForm from '@/components/RegisterForm.vue'
</script>

Features ​

🔄 Tab Navigation ​

  • Seamless switching between signup and login
  • Smooth tab transitions
  • Visual active state indicators

📝 Form Validation ​

  • Required field validation
  • Email format validation
  • Password strength requirements
  • Real-time error feedback

🔐 Authentication Integration ​

  • Supabase authentication
  • Email/password signup and login
  • Loading states during authentication
  • Error handling and display

🎨 Modern UI Design ​

  • Clean, professional styling
  • Dark mode support
  • Responsive design
  • Custom input styling

Component Structure ​

vue
<template>
  <div class="min-h-screen flex items-center justify-center p-4">
    <div class="card-Clawplate w-full max-w-md p-8 space-y-6">
      
      <!-- Tab Navigation -->
      <div class="flex items-center bg-gray-900 rounded-lg p-1">
        <button 
          v-for="tab in tabs"
          @click="activeTab = tab.id"
          :class="tabClasses(tab.id)"
        >
          {{ tab.label }}
        </button>
      </div>
      
      <!-- Form Title -->
      <div class="text-center">
        <h1>{{ activeTab === 'signup' ? title : 'Welcome back' }}</h1>
        <p>{{ subtitle }}</p>
      </div>
      
      <!-- Signup Form -->
      <form v-if="activeTab === 'signup'" @submit.prevent="handleSignup">
        <!-- Form fields -->
      </form>
      
      <!-- Login Form -->
      <form v-else @submit.prevent="handleLogin">
        <!-- Form fields -->
      </form>
      
    </div>
  </div>
</template>

Component API ​

Props ​

PropTypeDefaultDescription
titlestring'Create Account'Main title for signup form
subtitlestringundefinedOptional subtitle text

Events ​

EventPayloadDescription
signup-success{ user, session }Emitted on successful signup
login-success{ user, session }Emitted on successful login
auth-error{ error }Emitted on authentication error

Data Properties ​

PropertyTypeDescription
activeTab'signup' | 'login'Currently active tab
formDataobjectForm input data
isLoadingbooleanLoading state
errorsobjectForm validation errors

Form Fields ​

Signup Form Fields ​

typescript
interface SignupFormData {
  firstName: string
  lastName: string
  email: string
  password: string
  confirmPassword: string
  acceptTerms: boolean
}

Login Form Fields ​

typescript
interface LoginFormData {
  email: string
  password: string
  rememberMe: boolean
}

Form Validation ​

Validation Rules ​

typescript
const validateForm = () => {
  const errors: any = {}
  
  // Email validation
  if (!formData.email) {
    errors.email = 'Email is required'
  } else if (!isValidEmail(formData.email)) {
    errors.email = 'Please enter a valid email'
  }
  
  // Password validation
  if (!formData.password) {
    errors.password = 'Password is required'
  } else if (formData.password.length < 8) {
    errors.password = 'Password must be at least 8 characters'
  }
  
  // Confirm password (signup only)
  if (activeTab.value === 'signup' && formData.password !== formData.confirmPassword) {
    errors.confirmPassword = 'Passwords do not match'
  }
  
  return errors
}

Error Display ​

vue
<div v-if="errors.email" class="text-red-500 text-xs mt-1">
  {{ errors.email }}
</div>

Authentication Logic ​

Signup Handler ​

typescript
const handleSignup = async () => {
  isLoading.value = true
  errors.value = {}
  
  try {
    const validationErrors = validateForm()
    if (Object.keys(validationErrors).length > 0) {
      errors.value = validationErrors
      return
    }
    
    const { data, error } = await supabase.auth.signUp({
      email: formData.email,
      password: formData.password,
      options: {
        data: {
          first_name: formData.firstName,
          last_name: formData.lastName
        }
      }
    })
    
    if (error) {
      errors.value.general = error.message
    } else {
      emit('signup-success', { user: data.user, session: data.session })
    }
  } catch (error) {
    errors.value.general = 'An unexpected error occurred'
  } finally {
    isLoading.value = false
  }
}

Login Handler ​

typescript
const handleLogin = async () => {
  isLoading.value = true
  errors.value = {}
  
  try {
    const { data, error } = await supabase.auth.signInWithPassword({
      email: formData.email,
      password: formData.password
    })
    
    if (error) {
      errors.value.general = error.message
    } else {
      emit('login-success', { user: data.user, session: data.session })
    }
  } catch (error) {
    errors.value.general = 'Login failed. Please try again.'
  } finally {
    isLoading.value = false
  }
}

Styling Details ​

Card Styling ​

vue
<div class="card-Clawplate w-full max-w-md p-8 space-y-6">

Custom card class with ClawPlate branding:

css
.card-Clawplate {
  @apply bg-white dark:bg-gray-900 rounded-2xl shadow-xl border border-gray-200 dark:border-gray-700;
}

Input Styling ​

vue
<input class="input-Clawplate w-full" />

Custom input class:

css
.input-Clawplate {
  @apply px-4 py-3 rounded-lg border border-gray-200 dark:border-gray-700 
         bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100
         focus:ring-2 focus:ring-blue-500 focus:border-transparent
         placeholder-gray-400 dark:placeholder-gray-500;
}

Tab Navigation Styling ​

vue
<button :class="[
  'flex-1 py-2 px-4 rounded-md text-sm font-medium transition-all duration-200',
  activeTab === tab.id 
    ? 'bg-blue-50 text-gray-950 border border-black' 
    : 'text-gray-400 hover:text-white'
]">

Dark Mode Support ​

Theme Classes ​

vue
<div class="dark:bg-gray-50 dark:text-gray-900">
  <!-- Light mode: default styles -->
  <!-- Dark mode: dark: prefixed styles -->
</div>

Input Dark Mode ​

vue
<input class="bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100" />

Loading States ​

Submit Button Loading ​

vue
<button 
  type="submit"
  :disabled="isLoading"
  class="w-full py-3 px-4 rounded-lg font-medium transition-all"
  :class="isLoading ? 'bg-gray-400 cursor-not-allowed' : 'bg-blue-600 hover:bg-blue-700'"
>
  <span v-if="isLoading" class="flex items-center justify-center">
    <Icon name="loading-spinner" class="animate-spin mr-2" />
    {{ activeTab === 'signup' ? 'Creating Account...' : 'Signing In...' }}
  </span>
  <span v-else>
    {{ activeTab === 'signup' ? 'Create Account' : 'Sign In' }}
  </span>
</button>

Customization ​

Custom Validation Rules ​

typescript
const customValidation = {
  email: (value: string) => {
    if (!value) return 'Email is required'
    if (!value.includes('@')) return 'Please enter a valid email'
    return null
  },
  password: (value: string) => {
    if (!value) return 'Password is required'
    if (value.length < 8) return 'Password must be at least 8 characters'
    if (!/(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/.test(value)) {
      return 'Password must contain uppercase, lowercase, and number'
    }
    return null
  }
}

Custom Styling Theme ​

vue
<!-- Purple theme -->
<div class="bg-purple-50 border-purple-200">
  <button class="bg-purple-600 hover:bg-purple-700">

<!-- Green theme -->
<div class="bg-green-50 border-green-200">
  <button class="bg-green-600 hover:bg-green-700">

Additional Form Fields ​

vue
<!-- Company field for signup -->
<div v-if="activeTab === 'signup'">
  <label class="block text-xs text-gray-400 mb-1">Company (Optional)</label>
  <input 
    v-model="formData.company"
    type="text" 
    class="input-Clawplate w-full" 
    placeholder="Your company"
  >
</div>

Integration Examples ​

With Router Navigation ​

vue
<script setup lang="ts">
const router = useRouter()

const handleSignupSuccess = ({ user, session }) => {
  // Redirect to dashboard after successful signup
  router.push('/dashboard')
}

const handleLoginSuccess = ({ user, session }) => {
  // Redirect to appropriate page after login
  const redirectTo = router.currentRoute.value.query.redirect || '/dashboard'
  router.push(redirectTo)
}
</script>

With Toast Notifications ​

vue
<script setup lang="ts">
const { toast } = useToast()

const handleAuthError = ({ error }) => {
  toast.error('Authentication Error', error.message)
}

const handleSignupSuccess = () => {
  toast.success('Account Created', 'Welcome to ClawPlate!')
}
</script>

Accessibility ​

Form Labels ​

vue
<label for="email" class="block text-xs text-gray-400 mb-1">
  Email Address
</label>
<input 
  id="email"
  v-model="formData.email"
  type="email"
  aria-describedby="email-error"
  aria-required="true"
/>
<div id="email-error" v-if="errors.email" class="text-red-500 text-xs mt-1">
  {{ errors.email }}
</div>

Keyboard Navigation ​

  • Tab order follows logical flow
  • Enter key submits forms
  • Escape key can close modals

Screen Reader Support ​

vue
<div role="tablist" aria-label="Authentication options">
  <button 
    role="tab"
    :aria-selected="activeTab === 'signup'"
    :tabindex="activeTab === 'signup' ? 0 : -1"
  >
    Sign Up
  </button>
</div>

Dependencies ​

  • Supabase - Authentication service
  • Vue 3 - Composition API
  • Tailwind CSS - Styling framework
  • Nuxt Icons - Loading and form icons

Built with love by mhdevfr