10 Svelte Form Best Practices for Production Apps
Production Forms Need More Than Validation
A form that passes Zod validation isn’t necessarily production-ready. Production forms need to handle edge cases, be accessible, perform well, and fail gracefully. Here are 10 practices we’ve applied across hundreds of forms.
1. Always Use Progressive Enhancement
Forms should work without JavaScript. In SvelteKit, this means using form actions (not API routes) and the use:enhance directive. When JS is available, the form submits via fetch and updates reactively. When JS fails to load (bad connection, CDN outage, corporate proxy), the form still works as a standard HTML POST.
<form method="POST" use:enhance>
<!-- Fields -->
<button type="submit">Submit</button>
</form> This is free with Superforms. The use:enhance directive is purely additive — remove it and the form still functions.
2. Validate on Both Client and Server
Client validation is for UX. Server validation is for security. Never skip either one. With Superforms and Zod, you define the schema once and it runs on both sides automatically. See our complete guide on client + server validation.
3. Show All Errors, Not Just the First
Don’t make users fix one error, resubmit, find the next error, fix, resubmit. Show all validation errors simultaneously. Zod’s chain-based validation returns all errors by default:
z.string()
.min(8, 'At least 8 characters')
.regex(/[A-Z]/, 'Need uppercase')
.regex(/[0-9]/, 'Need a number') Users see all three requirements at once and can fix them in one pass. This is especially important for password fields — see our signup form tutorial.
4. Disable the Submit Button During Submission
Prevent double-submits with disabled={$submitting}. This handles:
- Impatient users clicking multiple times
- Slow connections where the response takes seconds
- Accidental double-taps on mobile
<button type="submit" disabled={$submitting}>
{$submitting ? 'Sending...' : 'Send'}
</button> Also consider adding a loading spinner or progress indicator for forms with file uploads or slow server processing.
5. Preserve Form State on Validation Errors
When validation fails, never clear the form. Users should see exactly what they typed, with errors highlighted next to the problematic fields. Superforms handles this by default — the form state round-trips through the server action.
For file inputs specifically, use withFiles({ form }) in your server action to preserve file selections across validation failures. See file upload forms for details.
6. Use Semantic HTML for Accessibility
Every input needs a <label> with a matching for attribute. Every error message needs aria-describedby linking it to its input. Use aria-invalid on inputs with errors.
<label for="email">Email</label>
<input
id="email"
name="email"
type="email"
aria-invalid={!!$errors.email}
aria-describedby={$errors.email ? 'email-error' : undefined}
/>
{#if $errors.email}
<p id="email-error" role="alert">{$errors.email}</p>
{/if} The role="alert" on error messages ensures screen readers announce errors as they appear.
7. Set Autocomplete Attributes
Help browsers (and password managers) fill forms correctly by setting autocomplete attributes:
<input name="email" type="email" autocomplete="email" />
<input name="password" type="password" autocomplete="new-password" />
<input name="name" autocomplete="name" />
<input name="phone" type="tel" autocomplete="tel" /> This improves completion rates significantly on mobile, where typing is slow and error-prone. The SvelteForms builder lets you set autocomplete attributes in the Advanced configuration tab.
8. Handle Network Errors Gracefully
Superforms’ use:enhance handles the happy path, but you should also handle network failures:
const { form, errors, enhance, submitting } = superForm(data.form, {
validators: zod(schema),
onError: ({ result }) => {
if (result.type === 'error') {
// Network error or server crash
alert('Something went wrong. Please try again.');
}
}
}); Consider implementing retry logic for critical forms (payment, signup) where a single failure shouldn’t lose the user’s data.
9. Rate Limit Server-Side Form Handlers
Every publicly accessible form is a target for abuse. At minimum, implement rate limiting on your form actions to prevent:
- Spam submissions flooding your inbox
- Brute-force login attempts
- Resource exhaustion from repeated file uploads
SvelteKit doesn’t include rate limiting out of the box, but Cloudflare (via Turnstile or rate limiting rules), Vercel (edge middleware), and libraries like rate-limiter-flexible all work.
10. Test Forms with Real User Behavior
Automated tests catch validation logic bugs, but they miss UX problems. Test your forms manually with these scenarios:
- Tab through every field — is the order logical?
- Submit with every field empty — do all errors show?
- Fill and submit on mobile — are tap targets large enough?
- Submit with slow network (DevTools throttling) — does the loading state work?
- Go back and forward in the browser — does form state persist?
- Try pasting into every field — do paste handlers work?
Skip the Boilerplate
These best practices are baked into the code that SvelteForms generates. Every exported form includes proper labels, error display, submit disabling, and Superforms integration. Start from a template and customize, or read the integration guide to drop generated code into your project.
More guides: Zod schema patterns, Superforms vs Felte comparison, contact form tutorial.
Build forms faster
Try the form builder →