Recipe: Schema Validation with Zod
Relevant source files
The following files were used as context for generating this wiki page:The @opensky/remotes package provides helper utilities, createValidation and createEnhancedForm, to improve the developer and user experience when working with SvelteKit's remote form functions. This recipe demonstrates how to combine these helpers with a Zod schema for robust, client-side and server-side form validation.
The core strategy is to use createValidation for managing the display logic of validation issues and createEnhancedForm for handling the form's submission lifecycle. A Zod schema is passed to the remote form's preflight() method, enabling SvelteKit to perform validation before the form is submitted. This combination provides a responsive and intuitive validation experience: issues appear when a user leaves a field (onblur) and are cleared as they type (oninput).
Sources: packages/remotes/README.md, packages/remotes/src/lib/validation.svelte.ts, packages/remotes/src/lib/enhance.svelte.ts
Core Components
The validation system is built upon two main functions exported by the package.
Sources: packages/remotes/src/lib/index.ts:1-2
createValidation
This helper is responsible for managing the validation state and user experience. It tracks which fields have issues and controls when those issues are displayed or cleared. It works by providing onblur and oninput event handlers that you attach to your form inputs.
The validation logic is designed to be user-friendly:
- On Blur: When a user navigates away from a field, a debounced validation call is made. If any issues are found, they are displayed.
- On Input: When a user starts typing in a field that has an issue, the issue is cleared immediately if the new input is valid according to a preflight check.
This prevents validation errors from appearing while the user is actively typing.
Sources: packages/remotes/README.md:5-9, packages/remotes/src/lib/validation.svelte.ts:153-181
The diagram below illustrates the validation flow for a single input field managed by createValidation.
The createValidation function returns an object with several methods to manage validation state.
| Method | Description |
|---|---|
fields(path) | Returns onblur and oninput handlers for a given field path (e.g., 'name' or 'address.city'). |
issues(path) | Returns an array of validation issue strings for a field path, or null if there are no issues. |
allIssues() | Returns an object containing all current validation issues for all registered fields. |
addIssue(path, issue) | Manually adds a custom validation error message to a specific field. |
reset() | Clears all validation issues from the state. |
validateAll() | Triggers validation for all registered fields against the server. |
updateIssues() | Populates the local issue state from the remote form's issues, typically after a server round-trip. |
Sources: packages/remotes/README.md:13-21, packages/remotes/src/lib/validation.svelte.ts:183-206
createEnhancedForm
This helper manages the form's submission lifecycle, tracking its state from submission to completion. It provides semantic callbacks for different stages of the request and can be integrated with createValidation to automatically handle validation updates.
Key features include:
- State Management: Tracks the form state (
idle,pending,delayed,timeout,issues,error,result). - Lifecycle Callbacks: Offers callbacks like
onSubmit,onReturn,onIssues, andonError. - Timing Control: Supports
delayMsandtimeoutMsoptions to handle long-running requests, exposingdelayedandtimeoutstates. - Validation Integration: When a
validationinstance is provided, it automatically callsupdateIssues()orvalidateAll()at the appropriate lifecycle stages.
Sources: packages/remotes/README.md:49-55, packages/remotes/src/lib/enhance.svelte.ts:223-230
The state machine for an enhanced form with both delayMs and timeoutMs configured is shown below.
Implementation Recipe
This section provides a step-by-step guide to implementing form validation using @opensky/remotes and Zod.
1. Define the Zod Schema
First, define a Zod schema that describes the shape and validation rules for your form data.
import { z } from 'zod';
const schema = z.object({
name: z.string().min(4, 'Too short').max(10, 'Too long'),
address: z.object({
state: z.string()
})
});
Sources: packages/remotes/README.md:120-126
2. Instantiate Helpers
In your Svelte component, import and instantiate createValidation and createEnhancedForm. Pass the remote form instance to both. To link them, provide the validation instance to the createEnhancedForm options.
import { createValidation, createEnhancedForm } from '@opensky/remotes';
import { myForm } from './myForm.remote'; // Your remote form definition
const valid = createValidation(myForm);
const form = createEnhancedForm(myForm, {
validation: valid,
delayMs: 500,
timeoutMs: 3500
});
Sources: packages/remotes/README.md:118-129
3. Enhance the Form
In the Svelte template, use the spread syntax to apply the remote form's preflight and enhance methods.
...myForm.preflight(schema): This is where the Zod schema is passed to SvelteKit, enabling client-side validation....form.enhance(...): This applies the enhanced form handler, allowing you to use its lifecycle callbacks.
<script lang="ts">
// ... imports and helper instantiations from step 2
</script>
<form {...myForm.preflight(schema).enhance(opts =>
form.enhance(opts, {
onReturn: ({ result }) => console.log('success', result)
})
)}>
<!-- Form fields go here -->
</form>
Sources: packages/remotes/README.md:133-138
4. Bind Fields and Display Issues
For each input, bind the remote form's field data and the validation event handlers.
{...myForm.fields.name.as('text')}: Binds the value, name, and other attributes from the remote form.{...valid.fields('name')}: Attaches theonblurandoninputevent handlers fromcreateValidation.valid.issues('name'): Use this to conditionally apply error styles or render error messages. The path must be a string, even for nested fields (e.g.,'address.state').
<input
{...myForm.fields.name.as('text')}
{...valid.fields('name')}
class:error={valid.issues('name')}
/>
{#if valid.issues('name')}
{#each valid.issues('name') as issue}
<p class="error">{issue}</p>
{/each}
{/if}
Sources: packages/remotes/README.md:139-149
Full Interaction Flow
The following sequence diagram shows the complete interaction from a user blurring an input to an error being displayed.
Conclusion
By combining createValidation, createEnhancedForm, and a Zod schema with SvelteKit's preflight functionality, you can build forms with a sophisticated and user-friendly validation system. This approach cleanly separates concerns: Zod defines the rules, createValidation manages the UI/UX of displaying errors, and createEnhancedForm handles the submission state and server communication. The result is a robust validation experience with minimal boilerplate.
Sources: packages/remotes/README.md, packages/remotes/src/lib/validation.svelte.ts, packages/remotes/src/lib/enhance.svelte.ts