After dialing in motion, it’s time to solidify the most critical flow: user input.
Key terms
- Form action: The server function SvelteKit runs when a form submits, letting you keep plain HTML behavior while performing validation and persistence.
- Progressive enhancement: An approach where the baseline (non-JavaScript) submission works first, then extra UX is layered on when possible.
use:enhance: A directive that intercepts form submissions to add loading states or toasts while keeping the action flow intact.fail: A helper that returns validation errors with an HTTP status so the client receives structured feedback.
Core ideas
Form actions are SvelteKit’s server-side handlers for form submissions. They preserve the HTML default submit while making server validation and state updates straightforward. Progressive enhancement ensures the form still works if JavaScript is momentarily unavailable. use:enhance honors that philosophy—it keeps the action semantics but adds a no-refresh UX. Validation must run on both client and server to stay safe; the server is the final authority.
Flow walkthrough
- The browser submits the form via HTML defaults. If
use:enhanceis attached, it intercepts the request briefly to inject loading UI. - The submission lands in
+page.server.tsas an action. Only here can you access the database or secrets. - Returning
{ success: true }populates theformprop on the client. Callingfail(400, ...)sends errors back to the same prop. - The client can show a
pendingstate until the response arrives, then render success or failure messages in place.
Walking through these steps slowly clarifies which code runs on the server and which runs in the browser.
Code examples
Combine a single action with use:enhance for a basic tasks page.
<!-- src/routes/tasks/+page.svelte -->
<script>
import { enhance } from '$app/forms';
export let form;
let pending = false;
</script>
<form method="POST" use:enhance={({ pending: p }) => (pending = p)}>
<input name="title" placeholder="Enter a task" required />
<button disabled={pending}>{pending ? 'Saving…' : 'Add'}</button>
{#if form?.errors?.title}
<p class="error" aria-live="polite">{form.errors.title}</p>
{/if}
</form>
// src/routes/tasks/+page.server.ts
export const actions = {
default: async ({ request }) => {
const data = await request.formData();
const title = data.get('title')?.toString().trim();
if (!title) {
return fail(400, { errors: { title: 'Please enter a title' } });
}
await db.task.create({ title });
return { success: true };
}
};
Submitting the form runs the server action. Any fail response is injected into the form prop so errors display inline. use:enhance manages the loading state and keeps the interaction smooth without a full reload.
UI preview
Form actions are easier to reason about when you picture the screen in each phase.
💬 댓글
이 글에 대한 의견을 남겨주세요