type: decision
status: active
timestamp: 2026-06-20
tags: [forms, decisions, architecture, web3forms, static-forms, tally]
status: active
timestamp: 2026-06-20
tags: [forms, decisions, architecture, web3forms, static-forms, tally]
Forms — trio (Web3Forms primary + Static Forms fallback + Tally for rich)
Vendor-redundant contact forms: Web3Forms + backup' primary, Static Forms fallback, both browser-only, both free unlimited). Tally handles rich / multi-step / conditional forms. Three roles, no overlap.
Forms — trio (Web3Forms primary + Static Forms fallback + Tally for rich)
Decision
The family runs three form services, each with a distinct role:
| Role | Service | Why |
|---|---|---|
| Contact form — primary | Web3Forms | Browser-only, domain-bound key, unlimited free |
| Contact form — fallback | Static Forms | Different vendor + edge; auto-swapped by <ContactForm> on Web3Forms failure |
| Rich / multi-step / conditional forms | Tally | Logic, conditional branches, payment integration, unlimited free |
Formspree stays documented as a second swap target but is not in the active rotation.
Why three and not one
- Web3Forms alone — single-vendor risk; if Web3Forms quotas tighten or has an outage, every contact form on every site goes dark.
- Tally for everything — overkill for a 3-field contact form;
the embed is heavier and the form lives at
tally.so/...rather than on-domain. - Static Forms alone — newer / smaller vendor than Web3Forms; better as a fallback than as the primary.
The trio mirrors the captcha-turnstile-plus-hcaptcha pattern: two free vendors on different infra for the high-availability surface, plus a specialist tool for the case the simple one doesn’t cover.
Implications
- One
<ContactForm>component in wraps both contact backends.providerprop oronErrorhandler swaps Web3Forms ? Static Forms transparently. - Tally embeds ship as a separate
<TallyForm formId="...">component in oriz-kit — no overlap with<ContactForm>. - Both contact backends are browser-only — no API keys leak
because both use domain-bound access keys; aligns with
rules/development/no-web3forms-server-side.md. - Anti-bot pairing —
<ContactForm>mounts the<Captcha>widget (Turnstile primary, hCaptcha fallback) inline. The token travels with the form payload to whichever backend handles the submit. - Per-site env-var kill-switch:
ENABLE_STATIC_FORMS=true|falselets a site disable the fallback if the access key isn’t set (yet) without breaking the primary path.
Cross-refs
- Forms services index
- Web3Forms — primary
- Static Forms — fallback
- Tally — rich forms
- Captcha pair decision
- No web3forms server-side rule
- No card-on-file rule