Form Components
Learn how to build robust forms in SvelteKit with progressive enhancement, validation, and error handling.
Forms are essential for user interaction in web applications. In SvelteKit, we use progressive enhancement with the use:enhance directive to provide a great user experience both with and without JavaScript.
Basic Form Structure
Here’s a basic form using Svelte 5 runes:
<script>
let name = $state('');
let email = $state('');
let errors = $derived(() => {
let errs = {};
if (!name.trim()) errs.name = 'Name is required';
if (!email.trim()) errs.email = 'Email is required';
else if (!email.includes('@')) errs.email = 'Invalid email format';
return errs;
});
let isValid = $derived(() => Object.keys(errors).length === 0);
</script>
<form method="POST" use:enhance>
<div>
<label for="name">Name</label>
<input id="name" name="name" bind:value={name} required />
{#if errors.name}
<span class="text-red-500 text-sm">{errors.name}</span>
{/if}
</div>
<div>
<label for="email">Email</label>
<input id="email" name="email" type="email" bind:value={email} required />
{#if errors.email}
<span class="text-red-500 text-sm">{errors.email}</span>
{/if}
</div>
<button type="submit" disabled={!isValid} class="btn btn-primary">
{#if form?.loading}
Submitting...
{:else}
Submit
{/if}
</button>
</form>Progressive Enhancement
The use:enhance directive automatically handles form submission, showing loading states and handling responses without requiring JavaScript.
Form Actions
In SvelteKit, form actions handle server-side processing. Create a +page.server.js file:
// +page.server.js
export const actions = {
default: async ({ request }) => {
const data = await request.formData();
const name = data.get('name');
const email = data.get('email');
// Validate server-side
if (!name || !email) {
return fail(400, { error: 'All fields are required' });
}
// Process data (save to database, etc.)
try {
// Your business logic here
await saveUser({ name, email });
return { success: true };
} catch (error) {
return fail(500, { error: 'Failed to save user' });
}
}
};File Uploads
For file uploads, use enctype="multipart/form-data":
<script>
let file = $state(null);
let preview = $state('');
$effect(() => {
if (file && file[0]) {
const reader = new FileReader();
reader.onload = (e) => preview = e.target.result;
reader.readAsDataURL(file[0]);
}
});
</script>
<form method="POST" enctype="multipart/form-data" use:enhance>
<div>
<label for="photo">Photo</label>
<input type="file" id="photo" name="photo" bind:files={file} accept="image/*" />
{#if preview}
<img src={preview} alt="Preview" class="w-32 h-32 object-cover mt-2" />
{/if}
</div>
<button type="submit" class="btn btn-primary">Upload</button>
</form>Error Handling
Handle errors from form actions in your component:
{#if form?.error}
<div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded">
{form.error}
</div>
{/if}
{#if form?.success}
<div class="bg-green-100 border border-green-400 text-green-700 px-4 py-3 rounded">
Form submitted successfully!
</div>
{/if}Validation
Combine client-side and server-side validation for the best user experience. Use $derived for reactive validation as shown in the basic example.