In my previous post I demonstrated how to submit form content to Supabase so you can start building your email list. Below, I will show how to add some basic validation and user feedback to help make sure the user is submitting useful information.
If you're using a chrome-based browser, you can check out the Stackblitz embed, otherwise visit here:
The final version will look like this:
This form will use two components, the signup form itself, as well as a feedback component which receives a prop from the form indicating what type of feedback to provide upon hitting the "submit" button and performing basic validation.
Let's go. 🏃♀️💨
Nuxt3 Template
<template>
<div>
<div class="flex items-center justify-center h-screen bg-black">
<div
class="text-center text-white p-6 border-2 border-white w-full max-w-md"
>
<h1 class="text-2xl mb-3">Join Our Newsletter</h1>
<p class="mb-6 text-lg">Get amazing updates right to your inbox.</p>
<form @submit.prevent="submitEmail" class="flex flex-col">
<label class="mb-4 text-left text-stone-400">
Name
<input
v-model="name"
type="text"
class="bg-gray-800 text-white w-full px-3 py-2 mt-2"
/>
</label>
<label class="mb-4 text-left text-stone-400">
Email
<input
v-model="email"
type="email"
class="bg-gray-800 text-white w-full px-3 py-2 mt-2"
/>
</label>
<label class="flex items-center text-white mb-4">
<input
type="checkbox"
v-model="consent"
class="mr-3"
id="updates"
/>
I agree to terms and conditions
</label>
<button
type="submit"
class="bg-white text-black px-6 py-2 mt-4"
@click.prevent="submitForm"
>
{{ isLoading ? 'Loading...' : 'Submit' }}
</button>
<feedback :formFeedback="formFeedback" />
</form>
</div>
</div>
</div>
</template>
Note: We will be using the Nuxt Tailwind module for styling.
The form has 3 input fields and a submit button. Each input is modeled to a ref value and the form submit button is linked to a submitForm
function.
Additionally, the button text is responsive to the isLoading
value, which we will handle in the script. Finally, you can see there is the 'feedback' component which accepts the formFeedback
ref value as a prop.
Nuxt3 Script Section
<script lang="ts" setup>
import { ref } from 'vue';
type FormFeedbackType = 'incomplete' | 'consent' | 'invalid' | 'success' | null;
const name = ref('');
const email = ref('');
const consent = ref(false);
const isLoading = ref(false);
const formFeedback: Ref<FormFeedbackType> = ref(null);
const success = ref(true);
const submitForm = async () => {
isLoading.value = true;
formFeedback.value = null;
if (!name.value.trim() || !email.value.trim()) {
formFeedback.value = 'incomplete';
isLoading.value = false;
return;
}
const regex = /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i;
if (email.value && !regex.test(email.value)) {
formFeedback.value = 'invalid';
success.value = false;
isLoading.value = false;
return;
}
if (!consent.value) {
formFeedback.value = 'consent';
success.value = false;
isLoading.value = false;
return;
}
setTimeout(() => {
// If the execution reaches here, it means that all checks have passed.
success.value = true;
formFeedback.value = 'success';
isLoading.value = false;
}, 1000);
};
</script>
The contact form component script includes ref values for each form field and state that is required for the form to function. Underneath the refs the async submitForm
function which handles the validation and submission logic. In our case we are not submitting form information to an actual database so the submission status is simulated by using a setTimeout
function.
As an bonus I created a new type for the formFeedback
ref which only allows certain strings to get passed to the feedback component. Anything other than the options defined in the type will result in a TypeScript compile-time error.
Form Feedback Component
The feedback component accepts a single prop, named 'feedbackType', which determines the type of feedback to be displayed. Based on the value of 'feedbackType', the component renders the corresponding div. The 'feedbackType' prop can only accept specific strings, as defined by the TypeScript type associated with the formFeedback ref.
<script lang="ts" setup>
const props = defineProps({
formFeedback: String,
});
</script>
<template>
<div
v-if="formFeedback === 'error'"
class="text-white w-full block p-3 mt-4 bg-red-500"
>
There was an error processing your request.
</div>
<div
v-else-if="formFeedback === 'success'"
class="text-white w-full block p-3 mt-4 bg-green-500"
>
Thanks for signing up!
</div>
<div
v-else-if="formFeedback === 'incomplete'"
class="text-white w-full block p-3 mt-4 bg-red-500"
>
Please complete all required fields.
</div>
<div
v-else-if="formFeedback === 'invalid'"
class="text-white w-full block p-3 mt-4 bg-red-500"
>
Please enter a valid email address.
</div>
<div
v-else-if="formFeedback === 'consent'"
class="text-white w-full block p-3 mt-4 bg-red-500"
>
Please agree to terms and conditions.
</div>
</template>
<style scoped></style>
This feedback component can be reused throughout your website for different forms - just make sure you update it to include different conditions.
App.vue
Maybe obvious but to be thorough, simply add the contact component to your app.vue
or desired page:
<template>
<div>
<contact />
</div>
</template>
Conclusion
That's it! You can now build a contact form with validation and feedback. 🎉
You can get the full code here - simply fork the project and start working on your own as needed.
Top comments (2)
This looks broken
<form @submit.prevent="submitEmail" class="flex flex-col">
submitEmail isn't defined
Apologies, this form doesn't actually submit emails—you need to connect it to Supabase for that. It is, however, set up for submission and error handling.