Try Live
Add Docs
Rankings
Pricing
Enterprise
Docs
Install
Theme
Install
Docs
Pricing
Enterprise
More...
More...
Try Live
Rankings
Create API Key
Add Docs
Svelte JSONSchema Form
https://github.com/x0k/svelte-jsonschema-form
Admin
A Svelte 5 library for generating forms from JSON schemas, serving as an unofficial port of
...
Tokens:
61,622
Snippets:
368
Trust Score:
6.4
Update:
3 days ago
Context
Skills
Chat
Benchmark
81.9
Suggestions
Latest
Show doc for...
Code
Info
Show Results
Context Summary (auto-generated)
Raw
Copy
Link
# svelte-jsonschema-form `svelte-jsonschema-form` (`@sjsf/form`) is a Svelte 5 library for generating reactive HTML forms from JSON Schema definitions. It is an unofficial port of `react-jsonschema-form`, designed to leverage Svelte 5's runes-based reactivity. The library follows a fully composable architecture where every concern—validation, schema merging, ID generation, translation, theming, and field resolution—is a pluggable, factory-based option passed to a central `createForm()` function that returns a reactive `FormState` object. The library ships as a monorepo of focused packages: `@sjsf/form` is the core, with separate packages for validators (`@sjsf/ajv8-validator`, `@sjsf/zod4-validator`, `@sjsf/valibot-validator`, `@sjsf/cfworker-validator`, `@sjsf/schemasafe-validator`), UI themes (`@sjsf/basic-theme`, `@sjsf/daisyui5-theme`, `@sjsf/flowbite3-theme`, `@sjsf/shadcn4-theme`, `@sjsf/skeleton4-theme`), and a SvelteKit integration (`@sjsf/sveltekit`). Every package is tree-shakeable and SSR-compatible. --- ## `createForm<T>(options)` — Create a reactive form instance The primary entry point. Returns a `FormState<T>` that drives all form components. All options are reactive (i.e., changing a derived state that feeds into options will re-derive the internal state). The type parameter `T` represents the expected validated output type. ```svelte <script lang="ts"> import { createForm, BasicForm, type Schema } from '@sjsf/form'; import { resolver } from '@sjsf/form/resolvers/basic'; import { translation } from '@sjsf/form/translations/en'; import { createFormMerger } from '@sjsf/form/mergers/modern'; import { createFormIdBuilder } from '@sjsf/form/id-builders/modern'; import { createFormValidator } from '@sjsf/ajv8-validator'; import { theme } from '@sjsf/basic-theme'; import '@sjsf/basic-theme/css/basic.css'; interface UserProfile { name: string; age: number; email: string; } const schema: Schema = { title: 'User Profile', type: 'object', properties: { name: { type: 'string', title: 'Full Name' }, age: { type: 'integer', title: 'Age', minimum: 0, maximum: 150 }, email: { type: 'string', title: 'Email', format: 'email' }, }, required: ['name', 'email'], }; const form = createForm<UserProfile>({ schema, theme, resolver, translation, merger: createFormMerger, // factory — Creatable<FormMerger, MergerFactoryOptions> validator: createFormValidator, // factory — Creatable<FormValidator<T>, ValidatorFactoryOptions> idBuilder: createFormIdBuilder, // factory — Creatable<FormIdBuilder, IdBuilderFactoryOptions> // Optional idPrefix: 'user-form', // default: 'root' initialValue: { name: 'Alice' }, // DeepPartial<T> disabled: false, fieldsValidationMode: ON_BLUR | AFTER_TOUCHED, onSubmit(value: UserProfile, e: SubmitEvent) { console.log('Submitted:', value); }, onSubmitError(result, e, formState) { console.error('Validation errors:', result.errors); }, onReset(e) { console.log('Form reset'); }, }); </script> <BasicForm {form} /> ``` --- ## `BasicForm` — All-in-one form component A convenience component that sets the form context, renders all fields, and adds a submit button. Accepts all standard `HTMLFormAttributes` in addition to the required `form` prop. ```svelte <script lang="ts"> import { createForm, BasicForm, type Schema } from '@sjsf/form'; // ... (imports omitted for brevity, see createForm example) const schema: Schema = { type: 'object', properties: { message: { type: 'string', title: 'Message' } }, required: ['message'], }; const form = createForm({ schema, theme, resolver, translation, merger: createFormMerger, validator: createFormValidator, idBuilder: createFormIdBuilder, onSubmit: (v) => alert(JSON.stringify(v)), }); </script> <!-- Passes extra HTML attributes to the <form> element --> <BasicForm {form} novalidate class="my-form" autocomplete="off" /> ``` --- ## `SimpleForm` — Single-component declarative form Accepts all `FormOptions<T>` directly as props and manages its own `createForm` internally. Automatically aborts pending submissions on destroy. ```svelte <script lang="ts"> import { SimpleForm, type Schema } from '@sjsf/form'; import { resolver } from '@sjsf/form/resolvers/basic'; import { translation } from '@sjsf/form/translations/en'; import { createFormMerger } from '@sjsf/form/mergers/modern'; import { createFormIdBuilder } from '@sjsf/form/id-builders/modern'; import { createFormValidator } from '@sjsf/ajv8-validator'; import { theme } from '@sjsf/basic-theme'; const schema: Schema = { type: 'object', properties: { query: { type: 'string', title: 'Search' } }, }; </script> <SimpleForm {schema} {theme} {resolver} {translation} merger={createFormMerger} validator={createFormValidator} idBuilder={createFormIdBuilder} onSubmit={(v) => console.log(v)} /> ``` --- ## `Form` + `Content` + `SubmitButton` — Composable form layout Use these primitives when you need custom layout, extra elements between fields, or programmatic control. ```svelte <script lang="ts"> import { createForm, Form, Content, SubmitButton, setFormContext, type Schema } from '@sjsf/form'; import { resolver } from '@sjsf/form/resolvers/basic'; import { translation } from '@sjsf/form/translations/en'; import { createFormMerger } from '@sjsf/form/mergers/modern'; import { createFormIdBuilder } from '@sjsf/form/id-builders/modern'; import { createFormValidator } from '@sjsf/ajv8-validator'; import { theme } from '@sjsf/basic-theme'; const schema: Schema = { type: 'object', properties: { username: { type: 'string', title: 'Username' }, password: { type: 'string', title: 'Password' }, }, required: ['username', 'password'], }; const form = createForm({ schema, theme, resolver, translation, merger: createFormMerger, validator: createFormValidator, idBuilder: createFormIdBuilder, onSubmit: (v) => console.log('Login:', v), }); setFormContext(form); </script> <Form {form}> <h2>Login</h2> <Content /> <p>By submitting you agree to our terms.</p> <SubmitButton /> </Form> ``` --- ## `Field` — Render a specific field by path Renders a single field at a typed JSON path within the form. Supports a `render` snippet for full custom rendering. ```svelte <script lang="ts"> import { createForm, setFormContext, Field, type Schema } from '@sjsf/form'; // ...standard imports omitted interface Address { street: string; city: string; zip: string } interface Order { shipping: Address; billing: Address } const schema: Schema = { type: 'object', properties: { shipping: { type: 'object', title: 'Shipping Address', properties: { street: { type: 'string', title: 'Street' }, city: { type: 'string', title: 'City' }, zip: { type: 'string', title: 'ZIP' }, }, required: ['street', 'city'], }, }, }; const form = createForm<Order>({ schema, theme, resolver, translation, merger: createFormMerger, validator: createFormValidator, idBuilder: createFormIdBuilder, }); setFormContext(form); </script> <!-- Render just the 'city' field inside a custom wrapper --> <div class="highlight-field"> <Field {form} path={['shipping', 'city']} /> </div> <!-- Custom render snippet for full control --> <Field {form} path={['shipping', 'zip']}> {#snippet render({ config, valueRef, uiOption, translate })} <label for={config.path.join('_')}>{config.title}</label> <input id={config.path.join('_')} bind:value={valueRef.current} /> {/snippet} </Field> ``` --- ## `handlers(form)` — Svelte attachment for native `<form>` elements Returns a Svelte attachment that wires submit and reset events to the form state, for use with custom `<form>` markup. ```svelte <script lang="ts"> import { createForm, Content, SubmitButton, handlers, setFormContext, type Schema } from '@sjsf/form'; // ...standard imports const schema: Schema = { type: 'object', properties: { note: { type: 'string' } } }; const form = createForm({ schema, theme, resolver, translation, merger: createFormMerger, validator: createFormValidator, idBuilder: createFormIdBuilder, onSubmit: console.log, }); setFormContext(form); </script> <!-- {@attach} is the Svelte 5 attachment syntax --> <form {@attach handlers(form)} novalidate> <Content /> <button type="submit">Save</button> <button type="reset">Reset</button> </form> ``` --- ## Form state queries and commands Functions that read or mutate the `FormState<T>` outside of templates. ```svelte <script lang="ts"> import { createForm, BasicForm, setValue, getValueSnapshot, updateErrors, updateFieldErrors, updateFieldErrorsByPath, getFieldErrors, getFieldErrorsByPath, hasErrors, getErrors, validate, validateAsync, type Schema, } from '@sjsf/form'; // ...standard imports const schema: Schema = { type: 'object', properties: { name: { type: 'string', title: 'Name' }, email: { type: 'string', title: 'Email' }, }, required: ['name'], }; const form = createForm({ schema, theme, resolver, translation, merger: createFormMerger, validator: createFormValidator, idBuilder: createFormIdBuilder, onSubmit: console.log, }); function prefill() { // Command: set form value (merges with schema defaults) setValue(form, { name: 'Bob', email: 'bob@example.com' }); } function snapshot() { // Query: get a plain-object snapshot of the current value const v = getValueSnapshot(form); console.log(v); // { name: 'Bob', email: 'bob@example.com' } } function injectErrors() { // Command: replace all errors from a ValidationError[] updateErrors(form, [ { path: ['email'], message: 'Already taken' }, ]); // Command: update errors for a specific path updateFieldErrors(form, form[Symbol.for('sjsf-root-path')], ['Root error']); updateFieldErrorsByPath(form, ['name'], ['Name is reserved']); } function readErrors() { // Query: get errors for a specific path console.log(getFieldErrorsByPath(form, ['email'])); // ['Already taken'] // Query: check if any errors exist console.log(hasErrors(form)); // true // Query: iterate all errors for (const [path, errors] of getErrors(form)) { console.log(path, errors); } } function syncValidate() { // Query: run synchronous form validation const result = validate(form); if (result.errors) { console.error(result.errors); } else { console.log('Valid:', result.value); } } async function asyncValidate() { const controller = new AbortController(); const result = await validateAsync(form, controller.signal); console.log(result); } </script> <BasicForm {form} /> <button onclick={prefill}>Prefill</button> <button onclick={snapshot}>Snapshot</button> <button onclick={injectErrors}>Inject Errors</button> ``` --- ## `FormState` properties — Reactive state flags and tasks The `FormState<T>` object exposes reactive flags and async task handles. ```svelte <script lang="ts"> import { createForm, BasicForm, type Schema } from '@sjsf/form'; // ...standard imports const schema: Schema = { type: 'object', properties: { title: { type: 'string', title: 'Title' } }, required: ['title'], }; const form = createForm({ schema, theme, resolver, translation, merger: createFormMerger, validator: createFormValidator, idBuilder: createFormIdBuilder, onSubmit: async (v) => { await fetch('/api/post', { method: 'POST', body: JSON.stringify(v) }); }, }); </script> <!-- form.isChanged: true after any user interaction before submit/reset --> {#if form.isChanged} <p class="unsaved">You have unsaved changes.</p> {/if} <!-- form.isSubmitted: true after first submission attempt --> {#if form.isSubmitted} <p>Form has been submitted.</p> {/if} <!-- form.submission.status: 'idle' | 'running' | 'delayed' | 'done' --> {#if form.submission.status === 'running'} <span>Submitting...</span> {/if} <!-- form.fieldsValidation.status --> {#if form.fieldsValidation.status === 'running'} <span>Validating...</span> {/if} <BasicForm {form} /> <!-- Programmatic submit / reset --> <button onclick={() => form.submit(new SubmitEvent('submit'))}>Submit</button> <button onclick={() => form.reset()}>Reset</button> ``` --- ## Fields validation mode — Trigger inline validation Bit-flag constants that control when per-field validation runs. Combine with bitwise OR. ```svelte <script lang="ts"> import { createForm, BasicForm, ON_INPUT, ON_CHANGE, ON_BLUR, ON_ARRAY_CHANGE, ON_OBJECT_CHANGE, AFTER_CHANGED, AFTER_TOUCHED, AFTER_SUBMITTED, type Schema, } from '@sjsf/form'; // ...standard imports const schema: Schema = { type: 'object', properties: { username: { type: 'string', title: 'Username', minLength: 3 }, email: { type: 'string', title: 'Email', format: 'email' }, }, required: ['username', 'email'], }; const form = createForm({ schema, theme, resolver, translation, merger: createFormMerger, validator: createFormValidator, idBuilder: createFormIdBuilder, // Validate on blur, but only after the user has interacted with the field fieldsValidationMode: ON_BLUR | AFTER_TOUCHED, // OR: validate on every keystroke after first submit // fieldsValidationMode: ON_INPUT | AFTER_SUBMITTED, onSubmit: console.log, }); </script> <BasicForm {form} /> ``` --- ## UI Schema — Customize field appearance and behavior The `uiSchema` option overrides titles, components, and options per field. Supports `$ref`-based reuse via `ui:definitions`. ```svelte <script lang="ts"> import { createForm, BasicForm, type Schema, type UiSchemaRoot } from '@sjsf/form'; // ...standard imports const schema: Schema = { type: 'object', properties: { bio: { type: 'string', title: 'Biography' }, role: { type: 'string', title: 'Role', enum: ['admin', 'user', 'guest'] }, priority: { type: 'integer', title: 'Priority', minimum: 1, maximum: 5 }, }, }; const uiSchema: UiSchemaRoot = { 'ui:globalOptions': { // Applied to every field unless overridden }, 'ui:definitions': { // Reusable UI schema fragments labeledInput: { 'ui:options': { title: 'Custom Label' } }, }, bio: { 'ui:components': { // Override the widget used by this field textWidget: 'textareaWidget', }, 'ui:options': { title: 'About You' }, }, role: { 'ui:options': { // Override translation strings for this field only translations: { 'add-array-item': 'Add role' }, }, }, priority: { 'ui:components': { integerField: 'rangeWidget' }, }, }; const form = createForm({ schema, uiSchema, theme, resolver, translation, merger: createFormMerger, validator: createFormValidator, idBuilder: createFormIdBuilder, onSubmit: console.log, }); </script> <BasicForm {form} /> ``` --- ## `createFormValidator` (Ajv8) — JSON Schema validation with Ajv The `@sjsf/ajv8-validator` package provides Ajv v8-based validation. Supports sync and async validation, per-field live validation, and precompiled schemas. ```typescript // src/lib/form-defaults.ts import { createFormValidator } from '@sjsf/ajv8-validator'; import type { FormOptions } from '@sjsf/form'; // Default (zero config) — uses Ajv with json-schema-form custom keywords export const validator = createFormValidator; // With custom Ajv instance import Ajv from 'ajv'; import addFormats from 'ajv-formats'; import { addFormComponents } from '@sjsf/ajv8-validator'; const ajv = addFormComponents(addFormats(new Ajv({ allErrors: true }))); export const customValidator = createFormValidator<{ name: string }>({ ajv, }); // Async validator (e.g., $async schemas) import { createAsyncFormValidator } from '@sjsf/ajv8-validator'; export const asyncValidator = createAsyncFormValidator<{ name: string }>({ ajv }); ``` --- ## `createFormValidator` (Zod v4) — Zod schema validation The `@sjsf/zod4-validator` package bridges Zod v4 schemas to `@sjsf/form`. Uses a `SchemaRegistry` to map JSON Schema `$id` values to Zod schemas. ```svelte <script lang="ts"> import { createForm, BasicForm } from '@sjsf/form'; import { adapt } from '@sjsf/zod4-validator/classic'; import * as z from 'zod'; // ...other standard imports (resolver, translation, merger, idBuilder, theme) // Use `adapt()` to get both the JSON Schema and a validator from a Zod schema const zodSchema = z.object({ name: z.string().min(1).meta({ title: 'Name' }), age: z.number().int().min(0).meta({ title: 'Age' }), email: z.string().email().meta({ title: 'Email' }), }).meta({ title: 'Registration' }); const { schema, validator } = adapt(zodSchema); const form = createForm({ schema, validator, // already instantiated — not a factory in this pattern theme, resolver, translation, merger: createFormMerger, idBuilder: createFormIdBuilder, onSubmit: (v) => console.log('Zod-validated:', v), }); </script> <BasicForm {form} /> ``` --- ## `createFormValidator` (Valibot) — Valibot schema validation The `@sjsf/valibot-validator` package bridges Valibot schemas using the same registry pattern as the Zod adapter. ```typescript import { createFormMerger } from '@sjsf/form/mergers/modern'; import { createFormIdBuilder } from '@sjsf/form/id-builders/modern'; import * as v from 'valibot'; import { createFormValidator, createSchemasRegistry, toJsonSchema, } from '@sjsf/valibot-validator'; // Define schema const userSchema = v.object({ name: v.pipe(v.string(), v.minLength(1)), email: v.pipe(v.string(), v.email()), }); // Build registry and JSON schema const schemaRegistry = createSchemasRegistry(); const { schema, id } = toJsonSchema(userSchema, schemaRegistry); // Create validator using the registry const validator = createFormValidator({ schemaRegistry, safeParse: v.safeParse }); // Pass to createForm as usual // createForm({ schema, validator, merger: createFormMerger, idBuilder: createFormIdBuilder, ... }) ``` --- ## Standard Schema adapter — Validation with any Standard Schema v1 library `@sjsf/form/validators/standard-schema` bridges any library implementing the [Standard Schema v1](https://github.com/standard-schema/standard-schema) specification. ```typescript import { createForm } from '@sjsf/form'; import { adapt, adaptAsync } from '@sjsf/form/validators/standard-schema'; // ...standard imports // `adapt` extracts the JSON Schema and creates a sync form value validator // Works with Zod, Valibot, ArkType and other Standard Schema compliant libraries import * as arktype from 'arktype'; const arktypeSchema = arktype.type({ name: 'string', email: 'string.email', }); // adapt() returns { schema, validator } where schema is a JSON Schema Draft-07 object const { schema, validator } = adapt(arktypeSchema); const form = createForm({ schema, validator, // wraps Standard Schema's validate() method // ...rest of options }); ``` --- ## `createFormMerger` — Schema and defaults merger The merger handles `allOf` resolution and populates form default values from the JSON Schema. It's a required factory in `createForm`. ```typescript import { createFormMerger } from '@sjsf/form/mergers/modern'; import type { FormOptions } from '@sjsf/form'; // Default usage (factory reference) const mergerFactory = createFormMerger; // With options const customMerger = (options: Parameters<typeof createFormMerger>[0]) => createFormMerger({ ...options, // Experimental default state behavior emptyObjectFields: 'populateRequiredDefaults', arrayMinItems: { populate: true, mergeExtraDefaults: true }, }); // Use in createForm: // createForm({ merger: createFormMerger, ... }) // or with custom options: // createForm({ merger: customMerger, ... }) ``` --- ## `createFormIdBuilder` (modern) — Field HTML id generation Generates HTML `id` attributes for form fields from their JSON paths. Required for label/input associations. ```typescript import { createFormIdBuilder } from '@sjsf/form/id-builders/modern'; // Default separator is '_' // Path ['user', 'address', 'city'] → 'root_user_address_city' const idBuilder = createFormIdBuilder; // factory reference // With custom options const customIdBuilder = (options: Parameters<typeof createFormIdBuilder>[0]) => createFormIdBuilder({ ...options, idPrefix: 'myform', // default: 'root' separator: '-', // default: '_' }); // Use in createForm: // createForm({ idBuilder: createFormIdBuilder, ... }) ``` --- ## `omitExtraData` — Strip form values not defined in schema Removes properties from the form value that are not present in the JSON Schema. Useful for cleaning data before submission. ```typescript import { createForm, getValueSnapshot, type Schema } from '@sjsf/form'; import { omitExtraData } from '@sjsf/form/omit-extra-data'; import { createFormValidator } from '@sjsf/ajv8-validator'; import { createFormMerger } from '@sjsf/form/mergers/modern'; const schema: Schema = { type: 'object', properties: { name: { type: 'string' }, email: { type: 'string' }, }, additionalProperties: false, }; // After createForm and user edits: // const form = createForm({ schema, validator: ..., merger: ..., ... }); // const rawValue = getValueSnapshot(form); // rawValue might contain extra keys if the schema changed // Access internal validator/merger from form state via FORM_VALIDATOR / FORM_MERGER symbols // (typically used in custom validators or submit handlers) declare const validator: ReturnType<typeof createFormValidator>; declare const merger: ReturnType<typeof createFormMerger>; declare const rawValue: unknown; const cleaned = omitExtraData(validator, merger, schema, rawValue); // → only keys defined in schema are kept ``` --- ## `preventPageReload` — Warn on unsaved changes A Svelte 5 effect that listens to `beforeunload` and shows a browser confirmation dialog when the form has unsaved changes. ```svelte <script lang="ts"> import { createForm, BasicForm, type Schema } from '@sjsf/form'; import { preventPageReload } from '@sjsf/form/prevent-page-reload'; // ...standard imports const schema: Schema = { type: 'object', properties: { draft: { type: 'string', title: 'Draft' } }, }; const form = createForm({ schema, theme, resolver, translation, merger: createFormMerger, validator: createFormValidator, idBuilder: createFormIdBuilder, onSubmit: console.log, }); // Call inside a component (requires $effect context) preventPageReload(form); // form.isChanged is monitored — browser dialog shown if user tries to navigate away </script> <BasicForm {form} /> ``` --- ## `convert` — Convert JSON Schema Draft 2020-12 to Draft 07 `@sjsf/form/converters/draft-2020-12` converts a Draft 2020-12 schema to Draft 07 format (which `@sjsf/form` uses internally). ```typescript import { convert } from '@sjsf/form/converters/draft-2020-12'; import type { JSONSchema } from 'json-schema-typed/draft-2020-12'; import { createForm, type Schema } from '@sjsf/form'; const draft2020Schema: JSONSchema.Interface = { $schema: 'https://json-schema.org/draft/2020-12/schema', type: 'object', prefixItems: [ { type: 'string', title: 'First' }, { type: 'number', title: 'Second' }, ], items: false, properties: { name: { type: 'string', title: 'Name' }, }, $defs: { address: { type: 'object', properties: { street: { type: 'string' } }, }, }, }; // Converts prefixItems → items, $defs → $defs, $dynamicRef → $ref, etc. const draft07Schema: Schema = convert(draft2020Schema); // Use draft07Schema with createForm as usual ``` --- ## File size validation — `createFileSizeValidator` An `AsyncFileListValidator` extension that validates file upload sizes against a `maxFileSizeBytes` UI option. ```typescript import { createForm, type Schema, type UiSchemaRoot } from '@sjsf/form'; import { createFileSizeValidator, formatFileSize, } from '@sjsf/form/validators/file-size'; import { createFormValidator as createAjvValidator } from '@sjsf/ajv8-validator'; // ...standard imports const schema: Schema = { type: 'object', properties: { avatar: { type: 'string', format: 'data-url', title: 'Avatar' }, }, }; const uiSchema: UiSchemaRoot = { avatar: { 'ui:options': { maxFileSizeBytes: 2 * 1024 * 1024 }, // 2 MB }, }; const fileSizeValidator = createFileSizeValidator( ({ file, maxSizeBytes }) => `${file.name} exceeds limit: ${formatFileSize(file.size)} > ${formatFileSize(maxSizeBytes)}`, { uiSchema } ); // Compose with AJV validator using Object.assign const validator = Object.assign( createAjvValidator(), fileSizeValidator ); const form = createForm({ schema, uiSchema, validator, theme, resolver, translation, merger: createFormMerger, idBuilder: createFormIdBuilder, onSubmit: console.log, }); ``` --- ## `createFormHandler` + `createAction` — SvelteKit form actions (server) `@sjsf/sveltekit/server` provides server-side helpers for processing `FormData` submissions through SvelteKit actions. ```typescript // src/routes/post/+page.server.ts import type { Actions } from '@sveltejs/kit'; import { createAction } from '@sjsf/sveltekit/server'; import { createFormMerger } from '@sjsf/form/mergers/modern'; import { createFormValidator } from '@sjsf/ajv8-validator'; import type { Schema } from '@sjsf/form'; import type { InitialFormData } from '@sjsf/sveltekit'; interface CreatePost { title: string; content: string } const schema: Schema = { type: 'object', properties: { title: { type: 'string', title: 'Title', minLength: 1, maxLength: 100 }, content: { type: 'string', title: 'Content', minLength: 1 }, }, required: ['title', 'content'], }; export const load = async () => ({ form: { schema, initialValue: { title: 'New post', content: '' }, } satisfies InitialFormData<CreatePost>, }); export const actions = { default: createAction( { name: 'form', // must match the key in `load` return schema, merger: createFormMerger, validator: createFormValidator, sendData: true, // include parsed data in failure response }, ({ title, content }: CreatePost, event, meta) => { // Return ValidationError[] to fail with field errors if (title.length > 100) { return [{ path: ['title'], message: 'Title is too long' }]; } // Return any object to merge into the success response return { post: { id: crypto.randomUUID(), title, content } }; } ), } satisfies Actions; ``` --- ## `SvelteKitForm` + `createMeta` — SvelteKit form action client The `@sjsf/sveltekit/client` package provides a `SvelteKitForm` component and `createMeta` helper that connect to SvelteKit form actions, handling error re-population and optimistic updates automatically. ```svelte <!-- src/routes/post/+page.svelte --> <script lang="ts"> import { createMeta, SvelteKitForm } from '@sjsf/sveltekit/client'; import { createFormIdBuilder } from '@sjsf/sveltekit'; import { resolver } from '@sjsf/form/resolvers/basic'; import { translation } from '@sjsf/form/translations/en'; import { createFormMerger } from '@sjsf/form/mergers/modern'; import { createFormValidator } from '@sjsf/ajv8-validator'; import { theme } from '@sjsf/basic-theme'; import type { ActionData, PageData } from './$types'; // createMeta<ActionData, PageData>() returns typed form metadata // .form refers to the 'form' key in load() return value const meta = createMeta<ActionData, PageData>().form; </script> <SvelteKitForm {meta} {theme} {resolver} {translation} merger={createFormMerger} validator={createFormValidator} idBuilder={createFormIdBuilder} onSuccess={(result) => { if (result.type === 'success') { console.log('Created post:', result.data?.post); } }} /> ``` --- ## `createServerValidator` + `form()` — SvelteKit Remote Functions (server) `@sjsf/sveltekit/rf/server` enables SvelteKit Remote Functions (experimental) for form handling. The server validator integrates with SvelteKit's `form()` remote function primitive. ```typescript // src/routes/post/data.remote.ts import { invalid } from '@sveltejs/kit'; import { form, query } from '$app/server'; import { createServerValidator } from '@sjsf/sveltekit/rf/server'; import { createFormMerger } from '@sjsf/form/mergers/modern'; import { createFormValidator } from '@sjsf/ajv8-validator'; import type { Schema } from '@sjsf/form'; import type { InitialFormData } from '@sjsf/sveltekit'; interface CreatePost { title: string; content: string } const schema: Schema = { type: 'object', properties: { title: { type: 'string', title: 'Title', minLength: 1 }, content: { type: 'string', title: 'Content', minLength: 1 }, }, required: ['title', 'content'], }; export const getInitialData = query(async () => ({ schema, initialValue: { title: 'New post', content: '' }, } satisfies InitialFormData<CreatePost>)); export const createPost = form( createServerValidator<CreatePost>({ schema, merger: createFormMerger, validator: createFormValidator, }), ({ data: { title, content } }) => { if (title.length > 100) { invalid({ path: ['title'], message: 'Title is too long' }); } return { id: crypto.randomUUID(), title, content }; } ); ``` --- ## `connect` — SvelteKit Remote Functions (client) `@sjsf/sveltekit/rf/client` provides `connect()` to wire a remote function's server validator back into `createForm`. ```svelte <!-- src/routes/post/+page.svelte --> <script lang="ts"> import { createForm, BasicForm } from '@sjsf/form'; import { createFormIdBuilder } from '@sjsf/sveltekit/rf'; import { connect } from '@sjsf/sveltekit/rf/client'; import { resolver } from '@sjsf/form/resolvers/basic'; import { translation } from '@sjsf/form/translations/en'; import { createFormMerger } from '@sjsf/form/mergers/modern'; import { theme } from '@sjsf/basic-theme'; import { createPost, getInitialData } from './data.remote'; // Fetch initial form data from server query const initialData = await getInitialData(); // connect() merges the remote function's schema/validator into form options const form = createForm( await connect(createPost, { ...initialData, theme, resolver, translation, merger: createFormMerger, idBuilder: createFormIdBuilder, }) ); </script> <BasicForm {form} novalidate /> ``` --- ## `getFormContext` / `setFormContext` — Access form state from child components Share form state across component trees without prop drilling. ```svelte <!-- ParentForm.svelte --> <script lang="ts"> import { createForm, Form, Content, SubmitButton, setFormContext, type Schema } from '@sjsf/form'; // ...standard imports const schema: Schema = { type: 'object', properties: { note: { type: 'string', title: 'Note' } }, }; const form = createForm({ schema, theme, resolver, translation, merger: createFormMerger, validator: createFormValidator, idBuilder: createFormIdBuilder, onSubmit: console.log, }); setFormContext(form); </script> <Form {form}> <Content /> <slot /> <!-- Custom child components can call getFormContext() --> <SubmitButton /> </Form> ``` ```svelte <!-- CustomStatus.svelte — a deeply nested child --> <script lang="ts"> import { getFormContext, hasErrors, getErrors } from '@sjsf/form'; const form = getFormContext(); </script> {#if form.isChanged} <span class="badge">Unsaved</span> {/if} {#if hasErrors(form)} <ul class="errors"> {#each [...getErrors(form)] as [path, msgs]} <li>{path.join('.')}: {msgs.join(', ')}</li> {/each} </ul> {/if} ``` --- ## Theme packages — UI component libraries Each theme package exports a `theme` constant that maps component types to Svelte components styled with the respective UI library. ```svelte <script lang="ts"> // Choose ONE theme package: // Basic (plain HTML, no CSS framework required) import { theme } from '@sjsf/basic-theme'; import '@sjsf/basic-theme/css/basic.css'; // daisyUI v5 (Tailwind CSS) // import { theme } from '@sjsf/daisyui5-theme'; // import '@sjsf/daisyui5-theme/styles.css'; // Flowbite Svelte v3 (Tailwind CSS) // import { theme } from '@sjsf/flowbite3-theme'; // import '@sjsf/flowbite3-theme/styles.css'; // Skeleton v4 (Tailwind CSS) // import { theme } from '@sjsf/skeleton4-theme'; // import '@sjsf/skeleton4-theme/styles.css'; // shadcn-svelte v4 (Tailwind CSS) // import { theme } from '@sjsf/shadcn4-theme'; // import '@sjsf/shadcn4-theme/styles.css'; import { createForm, BasicForm, type Schema } from '@sjsf/form'; import { resolver } from '@sjsf/form/resolvers/basic'; import { translation } from '@sjsf/form/translations/en'; import { createFormMerger } from '@sjsf/form/mergers/modern'; import { createFormIdBuilder } from '@sjsf/form/id-builders/modern'; import { createFormValidator } from '@sjsf/ajv8-validator'; const schema: Schema = { type: 'object', properties: { name: { type: 'string', title: 'Name' } }, required: ['name'], }; const form = createForm({ schema, theme, resolver, translation, merger: createFormMerger, validator: createFormValidator, idBuilder: createFormIdBuilder, onSubmit: console.log, }); </script> <BasicForm {form} /> ``` --- ## Summary `svelte-jsonschema-form` is suited for any application that renders dynamic, schema-driven forms in Svelte 5. Its primary use cases are: building admin panels and CRUD UIs where the schema comes from a backend API; multi-step wizards where each step is a sub-schema; configuration editors with `oneOf`/`anyOf` discriminated unions; and full-stack SvelteKit applications that need type-safe form submission with progressive enhancement (works without JavaScript). The library's composable design means you can mix and match validator strategies per form—use Ajv for one form, Zod for another—and the schema-driven approach makes forms automatically stay in sync with data model changes. Integration patterns follow a consistent factory model: you export a set of "defaults" (resolver, theme, translation, merger factory, validator factory, id builder factory) from a shared module and spread them into every `createForm` call, overriding only what differs per form. For SvelteKit projects, `@sjsf/sveltekit` handles both the traditional form actions pattern (server-side `createAction`, client-side `SvelteKitForm`) and the experimental Remote Functions pattern (`createServerValidator` + `connect`). Custom component integration is achieved by extending the `Theme` type with additional component types using TypeScript module augmentation, giving themes type-safe access to all field components, widgets, and templates.