Recipe: Styling Forms Based on State
Relevant source files
The following files were used as context for generating this wiki page:The @opensky/remotes package provides two primary utilities, createValidation and createEnhancedForm, designed to improve the experience of working with SvelteKit's remote form functions. These helpers enable developers to build responsive user interfaces by providing reactive state for both individual field validation and the overall form submission lifecycle. This allows for dynamic styling and behavior adjustments based on the form's current status, such as disabling buttons during submission or highlighting fields with validation errors.
This recipe demonstrates how to leverage these utilities to style form elements. We will cover styling based on field-specific validation issues and styling based on the global state of the form submission process.
Sources: README.md:3-4, packages/remotes/src/lib/index.ts:1-2
Styling Based on Field Validation State
The createValidation helper is used to manage validation issues for form fields. It provides a user-friendly validation experience where issues appear when a user leaves a field (onblur) and are cleared as the user types (oninput). This prevents validation errors from appearing prematurely while the user is still typing.
Sources: README.md:6-8
Key createValidation Methods for Styling
| Method | Description |
|---|---|
fields(path: string) | Returns onblur and oninput event handlers to attach to a form input. These handlers manage the validation logic for the specified field path. |
issues(path: string) | Returns an array of validation issue strings for a given field path, or null if there are no issues. This is the primary method used for conditional styling. |
addIssue(path: string, issue: string) | Manually adds a custom validation error to a field, which can be useful for client-side validation logic before submission. |
Sources: README.md:13-17, packages/remotes/src/lib/validation.svelte.ts:110-118, packages/remotes/src/lib/validation.svelte.ts:153-159
Field Validation Flow
The following diagram illustrates the logic flow for displaying and clearing validation issues on a single form field using the handlers provided by valid.fields(path).
This diagram shows how oninput and onblur events trigger different validation paths to update the UI.
Sources: packages/remotes/src/lib/validation.svelte.ts:128-151
Implementation Example
To style an input based on its validation status, you spread the handlers from valid.fields() onto the input element and use the result of valid.issues() to conditionally apply a CSS class.
<script lang="ts">
import { createValidation } from '@opensky/remotes';
import { myForm } from './myForm.remote';
const valid = createValidation(myForm);
</script>
<!-- Apply the 'error' class if there are any issues for the 'name' field -->
<input
{...myForm.fields.name.as('text')}
{...valid.fields('name')}
class:error={valid.issues('name')}
/>
<!-- Conditionally render the list of validation messages -->
{#if valid.issues('name')}
{#each valid.issues('name') as issue}
<p class="error-message">{issue}</p>
{/each}
{/if}
Sources: README.md:22-33, README.md:120-132
Styling Based on Overall Form State
The createEnhancedForm helper tracks the state of the entire form submission process. It exposes a reactive state variable and several boolean getters that simplify conditional rendering and styling based on the form's lifecycle.
Sources: packages/remotes/README.md:73-77
Form Submission States
The createEnhancedForm helper manages a state machine for the form submission. The state transitions based on user actions and server responses.
| State | Boolean Getter | Description |
|---|---|---|
idle | form.idle | The form is ready for submission. |
pending | form.pending | The form has been submitted and is awaiting a response. |
delayed | form.delayed | The submission is taking longer than delayMs. (Only available if delayMs is configured). |
timeout | form.timeout | The submission has exceeded the timeoutMs. (Only available if timeoutMs is configured). |
issues | form.issues | The server responded with validation issues. |
error | form.error | The submission resulted in an unhandled error. |
result | form.result | The submission was successful and the server returned a result. (Formerly success). |
Sources: packages/remotes/src/lib/enhance.svelte.ts:29-30, packages/remotes/src/lib/enhance.svelte.ts:319-335, packages/remotes/CHANGELOG.md:11
Form State Machine Diagram
This diagram visualizes the possible states of an enhanced form and the transitions between them.
This state machine shows the flow from Idle through various submission states and back to Idle.
Sources: packages/remotes/src/lib/enhance.svelte.ts:241-314, packages/remotes/CHANGELOG.md:7
Implementation Example
The boolean state getters are useful for disabling elements or showing loading indicators. For example, you can disable the submit button while the form is in a pending or delayed state to prevent multiple submissions.
<script lang="ts">
import { createEnhancedForm } from '@opensky/remotes';
import { myForm } from './myForm.remote';
const form = createEnhancedForm(myForm, {
delayMs: 500,
timeoutMs: 3500
});
</script>
<form {...myForm.preflight(schema).enhance(opts => form.enhance(opts))}>
<!-- form fields -->
<button disabled={form.pending || form.delayed}>
{#if form.delayed}
Loading...
{:else}
Submit
{/if}
</button>
</form>
{#if form.timeout}
<p>This is taking longer than expected...</p>
{/if}
{#if form.error}
<p>An error occurred.</p>
{/if}
{#if form.result}
<p>Success!</p>
{/if}
Sources: packages/remotes/README.md:104-114, packages/remotes/README.md:134-142
Combined Example
By using createValidation and createEnhancedForm together, you can create a comprehensive and responsive form UI. The validation instance is passed to createEnhancedForm to automate the updating of validation issues after a submission.
<script lang="ts">
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'),
address: z.object({ state: z.string() })
});
// 1. Create validation helper
const valid = createValidation(myForm);
// 2. Create enhanced form, passing the validation helper
const form = createEnhancedForm(myForm, {
validation: valid,
delayMs: 500,
timeoutMs: 3500
});
</script>
<!-- 3. Display global form state -->
<p>State: {form.state}</p>
<form {...myForm.preflight(schema).enhance(opts =>
form.enhance(opts, {
onReturn: ({ result }) => console.log('success', result)
})
)}>
<!-- 4. Style input based on field validation 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}
<!-- 5. Style button and text based on global form state -->
<button disabled={form.pending || form.delayed}>
{form.delayed ? 'Loading...' : 'Submit'}
</button>
</form>
<!-- 6. Show messages based on global form state -->
{#if form.timeout}
<p>Request timed out</p>
{/if}
This example demonstrates how to combine both helpers to control styling at both the field and form level.
Sources: README.md:116-163, packages/remotes/src/lib/enhance.svelte.ts:223-224
Summary
The @opensky/remotes package provides a robust system for managing and reacting to form state in SvelteKit. By using createValidation, developers can apply styles based on per-field validation issues for an improved UX. createEnhancedForm complements this by tracking the overall submission lifecycle, allowing for global UI changes like disabling buttons or showing loading/error messages. Together, they enable the creation of dynamic, user-friendly forms that provide clear feedback throughout the submission process.