Installation
Install the dependencies
Copy the component
Copy the following files into src/lib/components/ui/form.
These files use the Nova style and Lucide icons. npx @sveltery/ui@latest add writes the style and icon library from your components.json.
<script lang="ts">
import { Form as Primitive, type FormProps } from '@sveltery/base/form';
let props: FormProps = $props();
</script>
<Primitive data-slot="form" {...props} /> <script lang="ts">
import type { Snippet } from 'svelte';
import { Form as Primitive, type FormErrorProps } from '@sveltery/base/form';
import { cn } from 'cn';
let { children: content, class: classProp, ...props }: FormErrorProps = $props();
</script>
<Primitive.Error role="alert" data-slot="form-error" {...props} class={cn('text-destructive text-sm font-normal', classProp)}>
{#snippet children(messages: string[])}
{#if content}
{@render (content as Snippet<[string[]]>)(messages)}
{:else if messages.length > 1}
<ul class="ml-4 flex list-disc flex-col gap-1">
{#each messages as message, index (index)}
<li>{message}</li>
{/each}
</ul>
{:else}
{messages[0]}
{/if}
{/snippet}
</Primitive.Error> export { default as Form } from './Form.svelte';
export { default as FormError } from './FormError.svelte'; Update the import paths
The component folders import each other with relative paths, so keep them side by side. If you place them elsewhere, change the imports to match your project setup.
Usage
<script lang="ts">
import { Field, FieldError, FieldLabel } from '#lib/components/ui/field';
import { Form, FormError } from '#lib/components/ui/form';
import { Input } from '#lib/components/ui/input';
</script> <Form method="POST">
<Field name="email">
<FieldLabel>Email</FieldLabel>
<Input type="email" required />
<FieldError />
</Field>
<FormError />
</Form> Form is Sveltery Base's Form. It renders a <form> and leaves the browser's constraint validation on, so an invalid form never submits, with or without JavaScript. With JavaScript, it cancels the browser's bubble for each control inside a Field and shows the message in that field's FieldError instead.
Errors
Pass errors to show errors from a server or a validation library, keyed by field name. Each Field shows the entry under its name, and editing the field hides it. A message under the empty key '' belongs to the whole form, and FormError shows it.
<Form {errors} values={submitted}>
<!-- Fields -->
<FormError />
</Form> values fills each control by name, so a form that comes back from the server keeps what the user typed.
Remote forms
Pass a SvelteKit remote form as form. Form takes its method, action and submit handling, each Field reads its issues by its name, and FormError shows the issues that belong to no field. See Remote forms.
<Form form={signup}>
<Field name="email">
<FieldLabel>Email</FieldLabel>
<Input {...signup.fields.email.as('email')} required />
<FieldError />
</Field>
<FormError />
</Form> Submitting
onsubmit runs only after every constraint passes. Read the values with FormData:
<Form
onsubmit={(event) => {
event.preventDefault();
save(Object.fromEntries(new FormData(event.currentTarget)));
}}
>
<!-- Fields -->
</Form> API Reference
Form
It takes every Form prop and every <form> attribute.
FormError
Shows the messages that belong to no field: a remote form's invalid('message'), or the errors entry under ''. One message renders as text and several as a list. It renders nothing without messages.