SvelteKit Forms: Back to Basics (And Why That's a Good Thing)
Before we talk about SvelteKit, we should touch on how React form handling works. I’m writing this to remind myself of the modern way to do it…
- You set up
useStatefor every input field - Write
onChangehandlers to update state on every keystroke - Add an
onSubmithandler to prevent the browser’s default behavior, construct a JSON payload, fire off afetchrequest - Manage loading state to disable the submit button
- Manually parse the response to show success or error messages.
That’s a lot jumps to be hooped.
Frameworks like Next.js have improved this with server actions, but the fundamental approach remains the same: a cage match with the browsers native form handling.
SvelteKit takes a completely different approach. It just uses regular HTML.
How SvelteKit Forms Work
In SvelteKit, you write a standard <form method="POST"> in your +page.svelte file. That’s your UI. Then, in a +page.server.ts file in the same directory, you export an actions object that handles the submission.
The server-side action receives a request object, extracts the form data, runs your logic, and returns either a redirect or a fail with validation errors. No client-side state management, no manual fetch calls, no JSON serialization.
// +page.server.ts
export const actions = {
default: async ({ request }) => {
const data = await request.formData();
const email = data.get('email');
const password = data.get('password');
if (!email) {
return fail(400, { email, missing: true });
}
// authenticate user...
throw redirect(303, '/dashboard');
}
};
If you’re a backend developer coming from Python or Ruby, this should feel familiar. The browser collects the data, posts it to a URL, and the server processes it. Stateless, reliable, and native.
Progressive Enhancement: The Best of Both Worlds
You might be thinking, “Okay, but I don’t want full page reloads every time someone submits a form.” Fair point. SvelteKit handles this with something called progressive enhancement. You add use:enhance to your form tag:
<form method="POST" use:enhance>
<!-- your inputs -->
</form>
That single directive tells SvelteKit to automatically intercept the submission, prevent the page reload, submit the data via fetch under the hood, and update the form prop with any validation errors returned from the server.
But wait there’s more.
If JavaScript fails to load or the user is on a terrible connection, the form still works. It falls back to the browser’s native POST request.
Your form doesn’t break just because a script didn’t load. That’s how the web is supposed to work.
The React approach treats the browser as a rendering target and reimplements everything in JavaScript. SvelteKit treats the browser as a platform and builds on top of what’s already there.
Now, don’t get me wrong. I’ll write React code any day of the week at the ole w2, but I am really liking how Svelte and SvelteKit has decided to approach these sorts of features.