Skip to content

Vue Forms

Building forms in Vue 3 is clean and declarative. By using the Composition API (<script setup>), we can bind form values with reactive states (ref or reactive) and submit submissions asynchronously to Lucid Forms.


Here is a full Vue 3 template that captures form input and submits it without reloading the browser window.

<script setup>
import { ref, reactive } from 'vue'
const form = reactive({
name: '',
email: '',
message: '',
_honeypot: '' // Spam trap
})
const status = ref('idle') // 'idle' | 'loading' | 'success' | 'error'
const errorMessage = ref('')
const handleSubmit = async () => {
status.value = 'loading'
errorMessage.value = ''
// Honeypot spam check
if (form._honeypot) {
status.value = 'success'
return
}
try {
const response = await fetch('https://api.lucidforms.co/f/{your-form-id}', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
},
body: JSON.stringify({
name: form.name,
email: form.email,
message: form.message
})
})
const responseData = await response.json()
if (response.ok) {
status.value = 'success'
// Reset form
form.name = ''
form.email = ''
form.message = ''
} else {
status.value = 'error'
errorMessage.value = responseData.error || 'Form submission failed.'
}
} catch (err) {
status.value = 'error'
errorMessage.value = 'A network error occurred. Please try again.'
}
}
</script>
<template>
<div class="form-container">
<h2>Contact Us</h2>
<div v-if="status === 'success'" class="alert-success">
✓ Thank you! Your submission was received successfully.
</div>
<div v-if="status === 'error'" class="alert-error">
✗ {{ errorMessage }}
</div>
<form @submit.prevent="handleSubmit">
<!-- Invisible spam field -->
<input
type="text"
v-model="form._honeypot"
class="hidden-honeypot"
tabindex="-1"
autocomplete="off"
/>
<div class="form-group">
<label for="name">Name</label>
<input type="text" id="name" v-model="form.name" required />
</div>
<div class="form-group">
<label for="email">Email</label>
<input type="email" id="email" v-model="form.email" required />
</div>
<div class="form-group">
<label for="message">Message</label>
<textarea id="message" v-model="form.message" required rows="4"></textarea>
</div>
<button type="submit" :disabled="status === 'loading'">
{{ status === 'loading' ? 'Sending...' : 'Send Message' }}
</button>
</form>
</div>
</template>
<style scoped>
.form-container {
max-width: 400px;
margin: 0 auto;
font-family: system-ui, sans-serif;
}
.form-group {
margin-bottom: 15px;
}
label {
display: block;
margin-bottom: 5px;
font-weight: 500;
}
input[type="text"],
input[type="email"],
textarea {
width: 100%;
padding: 8px;
border: 1px solid #ccc;
border-radius: 4px;
box-sizing: border-box;
}
.hidden-honeypot {
display: none;
}
button {
padding: 10px 15px;
background-color: #42b883;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
button:disabled {
opacity: 0.6;
}
.alert-success {
color: #2e7d32;
background-color: #e8f5e9;
padding: 10px;
border-radius: 4px;
margin-bottom: 15px;
}
.alert-error {
color: #c62828;
background-color: #ffebee;
padding: 10px;
border-radius: 4px;
margin-bottom: 15px;
}
</style>