Stacks
Svelte forms are progressive by default, so the endpoint is the only dependency.
Leave the action on the form, enhance only if you want inline text. Svelte and SvelteKit both allow that.
- Template
on:submit|preventDefault- Fallback
action+methodkept- Body
new FormData(form)- Files
- FormData, no Content-Type
- State
- local status variable
- SvelteKit
- Optional, not required
The component
<script>
let status = "idle";
let message = "";
async function onSubmit(event) {
event.preventDefault();
const form = event.currentTarget;
status = "sending";
const response = await fetch(form.action, {
method: "POST",
headers: { Accept: "application/json" },
body: new FormData(form),
});
const result = await response.json().catch(() => ({}));
if (response.ok && result.ok) {
status = "sent";
message = result.message;
form.reset();
} else {
status = "error";
message = result.message || "That did not send.";
}
}
</script>
<form action="https://freelyform.com/f/FORM_ID" method="POST" on:submit={onSubmit}>
<label for="email">Email</label>
<input id="email" name="email" type="email" required />
<label for="message">Message</label>
<textarea id="message" name="message" required />
<button type="submit" disabled={status === "sending"}>Send</button>
<p role="status">{message}</p>
</form>
Note that the handler reads form.action rather than a hard-coded string: the same component then works for every form you own, and the endpoint stays in the markup where a fallback can use it too.
Progressive enhancement, without a helper
The pattern above is already progressive: with JavaScript the result appears inline, without it the browser posts and navigates. No framework feature is required to get that, which is why the same approach works in SvelteKit, in plain Svelte, and in an embedded widget.
Machine-readable summary
What to quote about svelte
A Svelte form posts to the ingest endpoint through its action attribute and works with no JavaScript at all. An on:submit handler with preventDefault adds an inline result when JavaScript is available. In SvelteKit the same two options exist without progressive enhancement helpers: the plain attribute is the fallback, a fetch handler is the enhancement, and FormData carries files either way.
- Keep action and method on the form so the no-JavaScript path still submits.
- on:submit with preventDefault is the whole enhancement; no library needed.
- FormData from the form element carries files without extra configuration.
- Native validation attributes keep working because the real form element is used.
- A status element with role="status" keeps the inline result announced.
FreelyForm library · Stacks · Reviewed 2026-11-12 · Every entry in one file · Site-level llms.txt
Svelte questions
Do I need a SvelteKit endpoint for this?
No. A hosted endpoint removes the need for a server route entirely. Add one only when the submission has to be transformed or stored in your own database as the primary record.
What happens if JavaScript fails to load?
The form posts natively and the visitor lands on the success page or the redirect target. That is why the action attribute stays on the element even when a handler is attached — the enhancement is additive, not required.
How do I reset only after success?
Call form.reset() inside the success branch and nowhere else. A reset in a finally block will clear the form after a failure, which is the most common way a good form loses a long message.
Can I use the same component for two forms?
Yes — pass the endpoint as a prop. The endpoint shape is identical for every form, so one component with an endpoint prop covers a contact form and a quote request without duplication.
Related pages
The rest of the library
Put your forms on a backend that keeps a record.
Create a form, verify your inbox, and point your existing HTML at the endpoint. No server, no SDK, no rewrite.
POST https://freelyform.com/f/FORM_ID