[Svelte Series 10] Set Up Form Actions and Validation Patterns

한국어 버전

After dialing in motion, it’s time to solidify the most critical flow: user input.

Key terms

  1. Form action: The server function SvelteKit runs when a form submits, letting you keep plain HTML behavior while performing validation and persistence.
  2. Progressive enhancement: An approach where the baseline (non-JavaScript) submission works first, then extra UX is layered on when possible.
  3. use:enhance: A directive that intercepts form submissions to add loading states or toasts while keeping the action flow intact.
  4. 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

  1. The browser submits the form via HTML defaults. If use:enhance is attached, it intercepts the request briefly to inject loading UI.
  2. The submission lands in +page.server.ts as an action. Only here can you access the database or secrets.
  3. Returning { success: true } populates the form prop on the client. Calling fail(400, ...) sends errors back to the same prop.
  4. The client can show a pending state 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.

http://localhost:5173/tasks
LIVE FORM

UI Preview

The flow should visually signal each phase

A form action isn’t just server code—it includes the UX that shows what’s happening before, during, and after submission.

use:enhancefail(400)pending

Idle

Before submit

Button enabled, no error text.

Submitting

While sending

Button disabled, 'Saving…' copy.

Error

Validation failed

Inline error message appears immediately.

Success

Saved

Input resets or success message appears.

  • Check: does the pending state prevent double-clicks?
  • Check: do failures appear in the same spot without reloading?
  • Check: after success, can users proceed naturally?

Why it matters

School contest submissions, club reports, or attendance corrections often trigger complaints the moment a form fails. Form actions pipe server validation results straight back to the UI, reducing mistakes. Progressive enhancement keeps the baseline submit working even if JavaScript temporarily fails.

Practice checklist

  • Follow along: add a default action under /tasks and return fail when the field is empty.
  • Extend it: define /toggle or /remove actions so one page handles multiple behaviors.
  • Debug: if form is undefined, confirm export let form; exists and the server file sits beside the page.
  • Completion criteria: use:enhance displays a pending state while success/error messages render without reloads.

Wrap-up

Once form actions and validation patterns are in place, input flows behave predictably. Next we’ll connect them with API integrations and optimistic UI patterns.

💬 댓글

이 글에 대한 의견을 남겨주세요