Add a codaForms form to your Astro site with zero JavaScript, a small client-side island, or a full React component — whichever fits the page.
Zero JavaScript authoring. Works with static builds.
Drop the embed script into any Astro page or component:
---
import Layout from '../layouts/Layout.astro';
---
<Layout title="Contact">
<h1>Contact Us</h1>
<!-- codaForms embed - that's it! -->
<script src="https://codasite.ai/forms-embed.js" data-form="cf_xxxxx"></script>
</Layout> Own the markup, submit to codaForms over fetch.
---
interface Props {
formId: string;
}
const { formId } = Astro.props;
---
<form id="contact-form" class="space-y-4">
<input name="name" placeholder="Name" required />
<input name="email" type="email" placeholder="Email" required />
<textarea name="message" placeholder="Message" rows="4" required></textarea>
<button type="submit">Send Message</button>
</form>
<div id="success-message" hidden>
<p>Thanks! We'll be in touch soon.</p>
</div>
<script define:vars={{ formId }}>
const form = document.getElementById('contact-form');
const successMessage = document.getElementById('success-message');
form.addEventListener('submit', async (e) => {
e.preventDefault();
const formData = new FormData(form);
const data = {
_formId: formId,
name: formData.get('name'),
email: formData.get('email'),
message: formData.get('message'),
};
const res = await fetch('https://codasite.ai/forms/api/submit', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
});
const result = await res.json();
if (result.success) {
form.hidden = true;
successMessage.hidden = false;
} else {
alert('Something went wrong. Please try again.');
}
});
</script> Use it in any page:
---
import Layout from '../layouts/Layout.astro';
import ContactForm from '../components/ContactForm.astro';
---
<Layout title="Contact">
<h1>Contact Us</h1>
<ContactForm formId="cf_xxxxx" />
</Layout> Use React when the form needs richer client state.
import { useState } from 'react';
export default function ContactForm({ formId }: { formId: string }) {
const [status, setStatus] = useState<'idle' | 'submitting' | 'success'>('idle');
async function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault();
setStatus('submitting');
const formData = new FormData(e.currentTarget);
const res = await fetch('https://codasite.ai/forms/api/submit', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
_formId: formId,
name: formData.get('name'),
email: formData.get('email'),
message: formData.get('message'),
}),
});
const result = await res.json();
if (result.success) setStatus('success');
}
if (status === 'success') {
return <p>Thanks! We'll be in touch soon.</p>;
}
return (
<form onSubmit={handleSubmit}>
<input name="name" placeholder="Name" required />
<input name="email" type="email" placeholder="Email" required />
<textarea name="message" placeholder="Message" required />
<button disabled={status === 'submitting'}>
{status === 'submitting' ? 'Sending...' : 'Send'}
</button>
</form>
);
} ---
import Layout from '../layouts/Layout.astro';
import ContactForm from '../components/ContactForm';
---
<Layout title="Contact">
<h1>Contact Us</h1>
<ContactForm client:load formId="cf_xxxxx" />
</Layout> Free account, unlimited forms, 500 submissions/mo.