File: src/blocks/contact/Contact01.astro
Contact section with heading, email/response note, and embedded ContactForm. Form backend is configured separately in integrations.ts → contactForm.
Config (blocks.ts + integrations.ts → contact)
Form provider settings live in integrations.ts → contactForm (not block props).
contact: {
width: "narrow",
heading: "Let's work together",
subheading:
"Fill out the form or email me directly. I take on a limited number of projects each quarter.",
email: "[email protected]",
responseNote: "Typically replies within 24 hours",
form: {
nameLabel: "Name",
emailLabel: "Email",
subjectLabel: "Subject",
messageLabel: "Tell me about your project",
buttonLabel: "Send message",
},
},
Add to a page
---
import Contact01 from "@/blocks/contact/Contact01.astro";
---
<Contact01 />
Props override matching keys from config:
<Contact01
heading="Custom headline"
/>
Form providers
Pick one backend in src/config/integrations.ts under contactForm.provider. ContactForm.astro calls resolveContactForm() in src/lib/contact-form.ts, which sets the form action, method, hidden fields, and provider-specific attributes (Netlify data-netlify, Cloudflare Turnstile, etc.).
Quick comparison
| Provider | Best for | Setup effort |
|---|---|---|
null / mailto |
Local demo, no backend | None (unreliable in production) |
formspree |
Quick launch, low traffic | Low |
formsubmit |
Free tier, no account | Low |
netlify |
Sites hosted on Netlify | Low (if on Netlify) |
cloudflare |
Same-domain POST, Turnstile, no third-party form SaaS | Medium |
Formspree
- Create a form at formspree.io
- Set
provider: "formspree"andformspreeIdto your form ID
contactForm: {
provider: "formspree",
formspreeId: "xyzabcde",
showSubject: true,
redirectPath: "/contact/",
},
Submissions are delivered to the email you configure in Formspree. The form POSTs directly to https://formspree.io/f/{formspreeId}.
FormSubmit
- Set
provider: "formsubmit"andformsubmitEmailto your inbox - Submit the form once and confirm the address via the email FormSubmit sends
contactForm: {
provider: "formsubmit",
formsubmitEmail: "[email protected]",
formsubmitSubject: "New contact form submission",
successRedirect: "/contact/?sent=1#contact-form",
showSubject: true,
},
successRedirect adds a hidden _next field so visitors return to your site with ?sent=1#contact-form after a successful submission. The inline script in ContactForm.astro shows the success message when that query param is present.
Netlify Forms
- Host the site on Netlify
- Set
provider: "netlify" - Deploy — Netlify detects
data-netlifyon the form at build time
contactForm: {
provider: "netlify",
formName: "contact",
redirectPath: "/contact/",
},
Submissions appear in the Netlify dashboard under Forms. A honeypot field (bot-field) is included automatically.
Cloudflare Worker
Recommended for production on Cloudflare when you want same-domain POST handling, server-side Turnstile verification, and no Formspree/FormSubmit dependency.
This pattern keeps the Astro site fully static while a Worker handles POST /contact/ on your domain. Included in theme packages:
- Server-side Turnstile verification
- Honeypot field (
website) - Email delivery via Cloudflare Email Routing /
send_emailbinding - Redirect back with
?sent=1#contact-form
Tradeoffs: more setup than Formspree (Wrangler, secrets, verified email destination); Cloudflare-specific — not portable to Vercel/Netlify without a different backend.
- Set in
integrations.ts:
contactForm: {
provider: "cloudflare",
action: "/contact/",
redirectPath: "/contact/",
turnstileSiteKeyPlaceholder: "__TURNSTILE_SITE_KEY__",
},
-
Copy
wrangler.jsonc.example→wrangler.jsoncand update domain +destination_address -
Set Worker secrets:
npx wrangler secret put TURNSTILE_SECRET_KEY
npx wrangler secret put TURNSTILE_SITE_KEY
- Build and deploy:
npm run build
npx wrangler deploy
The Worker replaces __TURNSTILE_SITE_KEY__ in HTML at runtime so the same static build works across environments. See src/worker.ts and Deploy for hosting details.
mailto fallback
When provider is null, the form falls back to mailto: — fine for demos, not recommended for production (behavior varies by browser and OS).
contactForm: {
provider: null,
},
Related files
| File | Purpose |
|---|---|
src/config/integrations.ts |
Provider selection and credentials |
src/lib/contact-form.ts |
Resolves action, method, hidden fields |
src/blocks/contact/ContactForm.astro |
Shared form markup |
src/blocks/contact/Contact01.astro |
Contact block wrapper |
src/worker.ts |
Optional Cloudflare handler (theme packages) |
wrangler.jsonc.example |
Example Worker config |
Props (Contact01)
| Prop | Config key | Type / values | Default / notes |
|---|---|---|---|
heading |
contact.heading |
string |
— |
subheading |
contact.subheading |
string |
— |
email |
contact.email |
string |
Passed through to ContactForm |
responseNote |
contact.responseNote |
string |
Shown beside the form |
Props (ContactForm)
Used inside Contact01. Rarely rendered standalone.
| Prop | Config key | Type / values | Default / notes |
|---|---|---|---|
email |
contact.email |
string |
Used by mailto / FormSubmit resolution |
redirectPath |
contactForm.redirectPath |
string |
Where to send visitors after submit |
showSubject |
contactForm.showSubject |
boolean |
Show optional subject field |
Form field labels live under contact.form in blocks.ts (not component props).
Also include: ContactForm.astro.
