Recipe: Building a Complete Form
Relevant source files
The following files were used as context for generating this wiki page:The @opensky/remotes package enhances SvelteKit's native remote form functions by providing two key helper utilities: createValidation and createEnhancedForm. This recipe demonstrates how to combine these two helpers to build a complete, robust, and user-friendly form with advanced state management, validation, and lifecycle callbacks, inspired by the ergonomics of libraries like Superforms.
This approach provides a superior user experience for validation, where issues appear on field blur and are cleared on input. It also introduces a structured way to handle the form submission lifecycle, including pending, delayed, and timeout states, as well as callbacks for success, validation issues, and errors.
Sources: README.md:3-4, packages/remotes/README.md:3-4, packages/remotes/src/lib/index.ts:1-2
Core Components
The solution is built upon two primary functions exported by the package.
Sources: packages/remotes/src/lib/index.ts:1-2
createValidation
This helper manages the client-side validation state and user experience. It works by attaching onblur and oninput event handlers to form fields. Validation issues are displayed when a user leaves a field (onblur) and are cleared as soon as the user starts typing again (oninput), preventing distracting error messages during input.
Sources: README.md:7-9, packages/remotes/src/lib/validation.svelte.ts:12-13
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 the form. |
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 submission. |
Sources: README.md:16-23, packages/remotes/src/lib/validation.svelte.ts:12-13, 148-181
The validation flow for a single field is illustrated below.
This diagram shows the user interaction flow and the corresponding validation logic triggered by onblur and oninput events managed by createValidation.
Sources: packages/remotes/src/lib/validation.svelte.ts:153-180
createEnhancedForm
This helper creates a state machine for the remote form, tracking its submission lifecycle and providing semantic callbacks. It simplifies handling different stages of a form request, such as loading, delays, and timeouts, and integrates with createValidation to automatically handle validation feedback from the server.
Sources: README.md:59-61, packages/remotes/src/lib/enhance.svelte.ts:40-42
Creation Options
createEnhancedForm is configured at creation time.
| Option | Type | Description |
|---|---|---|
validation | Validation | An optional instance returned from createValidation for automatic issue handling. |
delayMs | number | Milliseconds to wait before the form state transitions to 'delayed'. |
timeoutMs | number | Milliseconds to wait before the form state transitions to 'timeout'. |
Sources: packages/remotes/src/lib/enhance.svelte.ts:54-62, commit-message.txt:3-11
Returned Object
The function returns an object to manage the form.
| Property/Method | Description |
|---|---|
enhance(params, callbacks) | The form enhancement handler to be used with SvelteKit's enhance. |
reset() | A function to manually reset the form's state to 'idle'. |
state | A reactive store holding the current state of the form ('idle', 'pending', 'delayed', etc.). |
idle, pending, issues, error, result | Boolean getters for the current form state. |
delayed | A boolean getter that is only available if delayMs was provided at creation. |
timeout | A boolean getter that is only available if timeoutMs was provided at creation. |
Sources: README.md:70-76, packages/remotes/src/lib/enhance.svelte.ts:143-181
Form State Machine
The createEnhancedForm helper manages the form's state through a defined lifecycle.
This diagram illustrates the possible state transitions of an enhanced form, from idle through submission states and back.
Sources: packages/remotes/src/lib/enhance.svelte.ts:46, 230-233, 252-255, 269-271
Step-by-Step Implementation
This section provides a complete example of using both helpers together.
Sources: README.md:111-113
1. Initialization
First, import the helpers and create instances for your remote form. Define a Zod schema for validation.
// src/routes/some-form/+page.svelte
import { createValidation, createEnhancedForm } from '@opensky/remotes';
import { myForm } from './myForm.remote';
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()
})
});
const valid = createValidation(myForm);
const form = createEnhancedForm(myForm, {
validation: valid,
delayMs: 500,
timeoutMs: 3500
});
Sources: README.md:114-131
2. Template Integration
In your Svelte component, use the valid and form instances to build the form.
- Spread
valid.fields('path')onto your inputs to attach the validation event handlers. - Use
valid.issues('path')to conditionally display error messages. - Use the
formstate getters (form.pending,form.delayed) to provide user feedback, like disabling the submit button. - Apply the
form.enhancehandler to SvelteKit'senhancefunction.
<!-- src/routes/some-form/+page.svelte -->
<p>State: {form.state}</p>
<form {...myForm.preflight(schema).enhance(opts =>
form.enhance(opts, {
onDelay: () => console.log('showing loader'),
onTimeout: () => console.log('request timeout'),
onReturn: ({ result }) => console.log('success', result)
})
)}>
<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}
<button disabled={form.pending || form.delayed}>
{form.delayed ? 'Loading...' : 'Submit'}
</button>
</form>
{#if form.timeout}
<p>Request timed out</p>
{/if}
Sources: README.md:133-167
Advanced Usage & Submission Flow
The enhance handler provides powerful callbacks to intercept and control the submission process.
Submission Lifecycle Callbacks
The form.enhance method accepts a callbacks object to hook into the form submission lifecycle.
This diagram shows the sequence of events during a form submission managed by createEnhancedForm, including state changes and callback invocations.
Sources: packages/remotes/src/lib/enhance.svelte.ts:229-281
Client-Side Controls
The onSubmit callback provides functions to perform client-side actions before the form is submitted to the server.
cancel(state?): Aborts the submission. You can optionally set the form state to'issues'or'error'. This is useful for custom client-side validation.updates(...queries): Provides queries or overrides for optimistic UI updates, which are then passed to SvelteKit'ssubmit().updates(...).
// Example of using onSubmit controls
form.enhance(opts, {
onSubmit: ({ cancel, updates, data }) => {
// Custom client-side checks before submission
if (!customValidationCheck(data)) {
valid.addIssue('fieldName', 'Custom validation failed');
cancel('issues'); // Cancel and set state to 'issues'
return;
}
// Optimistic updates
updates(getPosts().withOverride((posts) => [newPost, ...posts]));
},
// ... other callbacks
})
Sources: README.md:91-102, packages/remotes/src/lib/enhance.svelte.ts:76-82, 240-244
Conclusion
By combining createValidation for a refined validation UX and createEnhancedForm for declarative state management, developers can build powerful and reliable forms in SvelteKit. This pattern abstracts away the boilerplate of handling form submission states and validation feedback, allowing developers to focus on the application logic within a structured and predictable framework. The result is a robust form that is easy to reason about and provides excellent feedback to the user throughout the submission process.
Sources: README.md:3-4, packages/remotes/README.md:3-4