Take a look at the SaaS template to see how you can build your authentication pages! (view source)
Usage
Built on top of the Form component, the AuthForm
component can be used in your pages or wrapped in a Card.
The form will construct itself based on the fields
prop and the state will be handled internally. You can pass all the props you would pass to a FormGroup or an Input to each field.
Use the providers
prop to add some Buttons above or below the form (depending on the align
prop) and the title
, description
, icon
props to customize the form (which can be overriden with slots).
Login
Enter your credentials to access your account.
or
<template>
<UAuthForm
title="Login"
description="Enter your credentials to access your account."
align="bottom"
icon="i-heroicons-user-circle"
:fields="[{ type: 'email', label: 'Email', name: 'email', placeholder: 'Enter your email', color: 'gray' }, { type: 'password', label: 'Password', name: 'password', placeholder: 'Enter your password', color: 'gray' }]"
:providers="[{ label: 'GitHub', icon: 'i-simple-icons-github', color: 'gray' }]"
:loading="false"
/>
</template>
As it is a Form underneath, you can handle the validation logic through the schema
or validate
props.
<script setup lang="ts">
import type { FormError } from '#ui/types'
const fields = [{
name: 'email',
type: 'text',
label: 'Email',
placeholder: 'Enter your email'
}, {
name: 'password',
label: 'Password',
type: 'password',
placeholder: 'Enter your password'
}, {
name: 'remember',
label: 'Remember me',
type: 'checkbox'
}]
const validate = (state: any) => {
const errors: FormError[] = []
if (!state.email) errors.push({ path: 'email', message: 'Email is required' })
if (!state.password) errors.push({ path: 'password', message: 'Password is required' })
return errors
}
const providers = [{
label: 'Continue with GitHub',
icon: 'i-simple-icons-github',
color: 'white' as const,
click: () => {
console.log('Redirect to GitHub')
}
}]
function onSubmit(data: any) {
console.log('Submitted', data)
}
</script>
<!-- eslint-disable vue/multiline-html-element-content-newline -->
<!-- eslint-disable vue/singleline-html-element-content-newline -->
<template>
<UCard class="max-w-sm w-full">
<UAuthForm
:fields="fields"
:validate="validate"
:providers="providers"
title="Welcome back!"
align="top"
icon="i-heroicons-lock-closed"
:ui="{ base: 'text-center', footer: 'text-center' }"
@submit="onSubmit"
>
<template #description>
Don't have an account? <NuxtLink to="/" class="text-primary font-medium">Sign up</NuxtLink>.
</template>
<template #password-hint>
<NuxtLink to="/" class="text-primary font-medium">Forgot password?</NuxtLink>
</template>
<template #validation>
<UAlert color="red" icon="i-heroicons-information-circle-20-solid" title="Error signing in" />
</template>
<template #footer>
By signing in, you agree to our <NuxtLink to="/" class="text-primary font-medium">Terms of Service</NuxtLink>.
</template>
</UAuthForm>
</UCard>
</template>
You can override each FormGroup slots by prefixing with the field name:
#password-hint
.Slots
icon
{}
title
{}
description
{}
validation
{}
footer
{}
Props
ui
DeepPartial<{ wrapper: string; base: string; container: string; title: string; description: string; icon: { wrapper: string; base: string; }; providers: string; form: string; footer: string; passwordToggle: { showIcon: string; hideIcon: string; }; default: { ...; }; }>
{}
title
string
undefined
icon
string
undefined
divider
string
"or"
align
"top" | "bottom"
"bottom"
validate
((state: any) => Promise<FormError<string>[]>) | ((state: any) => FormError<string>[])
undefined
description
string
undefined
providers
(Button & { click?: (...args: any[]) => void; })[]
[]
passwordToggle
Button
{}
fields
{ name: string; type: string; label: string; description?: string; help?: string; hint?: string; size?: FormGroupSize; placeholder?: string; required?: boolean; value?: string | number; readonly?: boolean; }[]
[]
submitButton
Button
{}
schema
any
undefined
validateOn
FormEventType[]
["submit"]
loading
boolean
false
Config
{
wrapper: 'w-full max-w-sm space-y-6',
base: '',
container: 'gap-y-6 flex flex-col',
title: 'text-2xl text-gray-900 dark:text-white font-bold',
description: 'text-gray-500 dark:text-gray-400 mt-1',
icon: {
wrapper: 'mb-2 pointer-events-none',
base: 'w-8 h-8 flex-shrink-0 text-gray-900 dark:text-white'
},
providers: 'space-y-3',
form: 'space-y-6',
footer: 'text-sm text-gray-500 dark:text-gray-400 mt-2',
default: {
submitButton: {
label: 'Continue'
}
}
}