Get the next one in your inbox.
One email when we publish something worth reading. No spam — appropriately enough, we'd know.
Build a Vue 3 <script setup> contact form that posts to a FormBridge endpoint with fetch() and shows an inline success or error message via a reactive ref.

Get an instant summary, key takeaways, action items, and answers to your questions about this article.
Build a Vue 3 <script setup> component with a form bound to @submit.prevent, then use fetch() to POST a FormData object to your FormBridge endpoint URL and update a reactive ref with the result — no server route or API layer is needed on the Vue side.
Vue's <script setup> syntax makes it easy to track a submission status without much ceremony. Here's a full contact form component that posts to a FormBridge endpoint and reports back to the user without a page reload:
<script setup>
import { ref } from 'vue'
const status = ref('idle') // idle | sending | success | error
async function handleSubmit(event) {
status.value = 'sending'
const form = event.target
const data = new FormData(form)
try {
const res = await fetch('https://app.formbridge.ai/api/forms/fb_8h2k9p', {
method: 'POST',
headers: { Accept: 'application/json' },
body: data,
})
if (res.ok) {
status.value = 'success'
form.reset()
} else {
status.value = 'error'
}
} catch {
status.value = 'error'
}
}
</script>
<template>
<form class="contact-form" @submit.prevent="handleSubmit">
<label for="full-name">Full name</label>
<input id="full-name" name="full-name" type="text" required />
<label for="email">Email</label>
<input id="email" name="email" type="email" required />
<label for="message">Message</label>
<textarea id="message" name="message" rows="5" required></textarea>
<button type="submit" :disabled="status === 'sending'">
{{ status === 'sending' ? 'Sending…' : 'Send message' }}
</button>
<p v-if="status === 'success'" role="status">Thanks — we'll be in touch shortly.</p>
<p v-if="status === 'error'" role="alert">Something went wrong. Please try again.</p>
</form>
</template>
You could skip the script entirely and just set action="https://app.formbridge.ai/api/forms/fb_8h2k9p" on the form — that works too, and FormBridge doesn't care which method you use. The reason to reach for fetch in a Vue app specifically is that you already have reactive state available, so it's cheap to turn a full-page navigation into an inline success or error message instead.
The Accept: application/json header matters here: FormBridge returns JSON on success, so checking res.ok and updating status is enough to drive the UI — there's no response body you need to parse for a simple form like this.
Nothing about using Vue changes what FormBridge expects. name="full-name", name="email", name="message" are ordinary attributes — FormBridge accepts any field name you send, and hyphenated names like full-name get auto-formatted into readable labels in your inbox and as merge tags ({{Full-Name}}) in notification emails.
No. A single ref() from the Composition API is enough to track idle, sending, success, and error states for a form this simple.
Both work. A plain action attribute is simpler and needs no JavaScript, while fetch() lets you intercept the response and show an inline status message instead of a full-page navigation.
It tells FormBridge to return a JSON response instead of relying on a redirect, which makes it easy to check res.ok and update your Vue component's state directly.
One email when we publish something worth reading. No spam — appropriately enough, we'd know.