Skip to content
BaseLayer Themes

Contact 01

Contact form with details column

Live preview

Let's work together

Fill out the form or email me directly. I take on a limited number of projects each quarter.

Or email [email protected]· Typically replies within 24 hours

File: src/blocks/contact/Contact01.astro

Contact section with heading, email/response note, and embedded ContactForm. Form backend is configured separately in integrations.tscontactForm.

Config (blocks.ts + integrations.tscontact)

Form provider settings live in integrations.tscontactForm (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

  1. Create a form at formspree.io
  2. Set provider: "formspree" and formspreeId to 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

  1. Set provider: "formsubmit" and formsubmitEmail to your inbox
  2. 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

  1. Host the site on Netlify
  2. Set provider: "netlify"
  3. Deploy — Netlify detects data-netlify on 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_email binding
  • 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.

  1. Set in integrations.ts:
contactForm: {
  provider: "cloudflare",
  action: "/contact/",
  redirectPath: "/contact/",
  turnstileSiteKeyPlaceholder: "__TURNSTILE_SITE_KEY__",
},
  1. Copy wrangler.jsonc.examplewrangler.jsonc and update domain + destination_address

  2. Set Worker secrets:

npx wrangler secret put TURNSTILE_SECRET_KEY
npx wrangler secret put TURNSTILE_SITE_KEY
  1. 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,
},
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.

← Contact versions · Blocks overview