Basic Form
A simple login form with email validation and minimum password length
Code Svelte
1
2<script lang="ts">
3 import { Schema, pipe } from "effect";
4 import { SchemaForm, FormController, withField, withFormLayout } from "@kareyes/aether/forms";
5
6 const LoginSchema = pipe(
7 Schema.Struct({
8 email: pipe(
9 Schema.String,
10 Schema.pattern(/^[^\s@]+@[^\s@]+\.[^\s@]+$/),
11 Schema.annotations({ message: () => "Please enter a valid email" }),
12 withField({
13 label: "Email",
14 inputType: "email",
15 placeholder: "you@example.com"
16 })
17 ),
18 password: pipe(
19 Schema.String,
20 Schema.minLength(8),
21 withField({
22 label: "Password",
23 inputType: "password",
24 placeholder: "Min 8 characters"
25 })
26 )
27 }),
28 withFormLayout({
29 columns: 1,
30 sections: [{ id: "login", title: "Login" }]
31 })
32 );
33
34 const controller = new FormController(LoginSchema, {
35 validateOnBlur: true,
36 validateOnChange: false,
37 initialValues: { email: "", password: "" }
38 });
39
40 async function handleSubmit(data) {
41 console.log("Login submitted:", data);
42 }
43</script>
44
45<SchemaForm
46 {controller}
47 onSubmit={handleSubmit}
48 submitText="Sign In"
49/>Multi-Section Form
A profile form with multiple sections using the card variant
Code Svelte
1
2<script lang="ts">
3 import { Schema, pipe } from "effect";
4 import { SchemaForm, FormController, withField, withFormLayout } from "@kareyes/aether/forms";
5
6 const ProfileSchema = pipe(
7 Schema.Struct({
8 firstName: pipe(
9 Schema.String,
10 Schema.minLength(1),
11 withField({
12 label: "First Name",
13 placeholder: "John",
14 section: "personal",
15 colSpan: 6
16 })
17 ),
18 lastName: pipe(
19 Schema.String,
20 Schema.minLength(1),
21 withField({
22 label: "Last Name",
23 placeholder: "Doe",
24 section: "personal",
25 colSpan: 6
26 })
27 ),
28 bio: pipe(
29 Schema.String,
30 withField({
31 label: "Bio",
32 inputType: "textarea",
33 placeholder: "Tell us about yourself...",
34 section: "personal"
35 })
36 ),
37 email: pipe(
38 Schema.String,
39 Schema.pattern(/^[^\s@]+@[^\s@]+\.[^\s@]+$/),
40 withField({
41 label: "Email",
42 inputType: "email",
43 section: "contact",
44 colSpan: 6
45 })
46 ),
47 phone: pipe(
48 Schema.String,
49 withField({
50 label: "Phone",
51 inputType: "tel",
52 mask: "phone",
53 placeholder: "(555) 123-4567",
54 section: "contact",
55 colSpan: 6
56 })
57 )
58 }),
59 withFormLayout({
60 columns: 12,
61 sections: [
62 { id: "personal", title: "Personal Information", order: 1 },
63 { id: "contact", title: "Contact Details", order: 2 }
64 ]
65 })
66 );
67
68 const controller = new FormController(ProfileSchema, {
69 validateOnBlur: true
70 });
71</script>
72
73<SchemaForm
74 {controller}
75 onSubmit={(data) => console.log("Profile:", data)}
76 sectionVariant="card"
77 submitText="Save Profile"
78/>Multi-Step Form
A wizard-style registration form with step validation and navigation
Code Svelte
1
2<script lang="ts">
3 import { Schema, pipe } from "effect";
4 import { SchemaForm, FormController, withField, withFormLayout } from "@kareyes/aether/forms";
5
6 const RegistrationSchema = pipe(
7 Schema.Struct({
8 email: pipe(
9 Schema.String,
10 Schema.pattern(/^[^\s@]+@[^\s@]+\.[^\s@]+$/),
11 withField({ label: "Email", inputType: "email", step: 1, section: "account" })
12 ),
13 password: pipe(
14 Schema.String,
15 Schema.minLength(8),
16 withField({ label: "Password", inputType: "password", step: 1, section: "account" })
17 ),
18 firstName: pipe(
19 Schema.String,
20 Schema.minLength(1),
21 withField({ label: "First Name", step: 2, section: "profile", colSpan: 6 })
22 ),
23 lastName: pipe(
24 Schema.String,
25 Schema.minLength(1),
26 withField({ label: "Last Name", step: 2, section: "profile", colSpan: 6 })
27 ),
28 role: pipe(
29 Schema.String,
30 withField({
31 label: "Role",
32 inputType: "select",
33 step: 2,
34 section: "profile",
35 options: [
36 { value: "developer", label: "Developer" },
37 { value: "designer", label: "Designer" },
38 { value: "manager", label: "Manager" }
39 ]
40 })
41 ),
42 newsletter: pipe(
43 Schema.Boolean,
44 withField({ label: "Subscribe to newsletter", step: 3, section: "preferences" })
45 )
46 }),
47 withFormLayout({
48 columns: 12,
49 sections: [
50 { id: "account", title: "Account" },
51 { id: "profile", title: "Profile" },
52 { id: "preferences", title: "Preferences" }
53 ],
54 steps: [
55 { step: 1, title: "Account", description: "Create your login" },
56 { step: 2, title: "Profile", description: "Personal details" },
57 { step: 3, title: "Finish", description: "Preferences" }
58 ]
59 })
60 );
61
62 const controller = new FormController(RegistrationSchema, {
63 validateOnBlur: true,
64 initialValues: { newsletter: false }
65 });
66</script>
67
68<SchemaForm
69 {controller}
70 onSubmit={(data) => console.log("Registered:", data)}
71 showStepIndicator={true}
72 nextText="Continue"
73 prevText="Go Back"
74 submitText="Complete Registration"
75/>Input Types
All supported input types: text, email, password, number, url, tel, textarea, select, radio, switch, and checkbox
Code Svelte
1
2<script lang="ts">
3 import { Schema, pipe } from "effect";
4 import { SchemaForm, FormController, withField, withFormLayout } from "@kareyes/aether/forms";
5
6 const InputShowcaseSchema = pipe(
7 Schema.Struct({
8 fullName: pipe(
9 Schema.String,
10 withField({ label: "Full Name", inputType: "text", placeholder: "John Doe", colSpan: 6 })
11 ),
12 email: pipe(
13 Schema.String,
14 withField({ label: "Email", inputType: "email", placeholder: "you@example.com", colSpan: 6 })
15 ),
16 password: pipe(
17 Schema.String,
18 withField({ label: "Password", inputType: "password", colSpan: 6 })
19 ),
20 age: pipe(
21 Schema.Number,
22 withField({ label: "Age", inputType: "number", colSpan: 6 })
23 ),
24 website: pipe(
25 Schema.String,
26 withField({ label: "Website", inputType: "url", placeholder: "https://example.com", colSpan: 6 })
27 ),
28 phone: pipe(
29 Schema.String,
30 withField({ label: "Phone", inputType: "tel", mask: "phone", colSpan: 6 })
31 ),
32 bio: pipe(
33 Schema.String,
34 withField({ label: "Biography", inputType: "textarea", placeholder: "Tell us about yourself..." })
35 ),
36 country: pipe(
37 Schema.String,
38 withField({
39 label: "Country",
40 inputType: "select",
41 colSpan: 6,
42 options: [
43 { value: "us", label: "United States" },
44 { value: "uk", label: "United Kingdom" },
45 { value: "ca", label: "Canada" },
46 { value: "au", label: "Australia" }
47 ]
48 })
49 ),
50 priority: pipe(
51 Schema.String,
52 withField({
53 label: "Priority",
54 inputType: "radio",
55 colSpan: 6,
56 options: [
57 { value: "low", label: "Low" },
58 { value: "medium", label: "Medium" },
59 { value: "high", label: "High" }
60 ]
61 })
62 ),
63 notifications: pipe(
64 Schema.Boolean,
65 withField({ label: "Enable notifications", inputType: "switch", colSpan: 6 })
66 ),
67 acceptTerms: pipe(
68 Schema.Boolean,
69 withField({ label: "I accept the terms and conditions", inputType: "checkbox", colSpan: 6 })
70 )
71 }),
72 withFormLayout({
73 columns: 12,
74 sections: [{ id: "main", title: "All Input Types" }]
75 })
76 );
77
78 const controller = new FormController(InputShowcaseSchema);
79</script>
80
81<SchemaForm
82 {controller}
83 onSubmit={(data) => console.log("Data:", data)}
84 submitText="Submit"
85/>Validation
Schema-level validation with custom messages, pattern matching, and required checkboxes
Code Svelte
1
2<script lang="ts">
3 import { Schema, pipe } from "effect";
4 import {
5 SchemaForm, FormController, withField, withFormLayout,
6 RequiredCheckbox, requiredCheckbox
7 } from "@kareyes/aether/forms";
8
9 const ValidationSchema = pipe(
10 Schema.Struct({
11 username: pipe(
12 Schema.String,
13 Schema.minLength(3),
14 Schema.maxLength(20),
15 Schema.pattern(/^[a-zA-Z0-9_]+$/),
16 Schema.annotations({
17 message: () => "Username must be 3-20 characters (letters, numbers, underscores)"
18 }),
19 withField({
20 label: "Username",
21 placeholder: "john_doe",
22 description: "3-20 characters, letters, numbers, underscores only",
23 colSpan: 6
24 })
25 ),
26 email: pipe(
27 Schema.String,
28 Schema.pattern(/^[^\s@]+@[^\s@]+\.[^\s@]+$/),
29 Schema.annotations({ message: () => "Please enter a valid email" }),
30 withField({ label: "Email", inputType: "email", colSpan: 6 })
31 ),
32 password: pipe(
33 Schema.String,
34 Schema.minLength(8),
35 Schema.annotations({ message: () => "Password must be at least 8 characters" }),
36 withField({
37 label: "Password",
38 inputType: "password",
39 description: "Minimum 8 characters"
40 })
41 ),
42 acceptTerms: pipe(
43 RequiredCheckbox,
44 withField({ label: "I accept the terms and conditions", inputType: "checkbox" })
45 ),
46 acceptPrivacy: pipe(
47 requiredCheckbox("You must accept the privacy policy"),
48 withField({ label: "I accept the privacy policy", inputType: "checkbox" })
49 )
50 }),
51 withFormLayout({
52 columns: 12,
53 sections: [{ id: "main", title: "Validation Examples" }]
54 })
55 );
56
57 const controller = new FormController(ValidationSchema, {
58 validateOnBlur: true,
59 validateOnChange: false
60 });
61</script>
62
63<SchemaForm
64 {controller}
65 onSubmit={(data) => console.log("Valid data:", data)}
66 submitText="Register"
67/>Section Variants - Card
Each section rendered inside a card container
Code Svelte
1
2<script lang="ts">
3 import { Schema, pipe } from "effect";
4 import { SchemaForm, FormController, withField, withFormLayout } from "@kareyes/aether/forms";
5
6 const SectionSchema = pipe(
7 Schema.Struct({
8 firstName: pipe(
9 Schema.String,
10 withField({ label: "First Name", section: "personal", colSpan: 6 })
11 ),
12 lastName: pipe(
13 Schema.String,
14 withField({ label: "Last Name", section: "personal", colSpan: 6 })
15 ),
16 email: pipe(
17 Schema.String,
18 withField({ label: "Email", inputType: "email", section: "contact" })
19 ),
20 phone: pipe(
21 Schema.String,
22 withField({ label: "Phone", inputType: "tel", section: "contact" })
23 )
24 }),
25 withFormLayout({
26 columns: 12,
27 sections: [
28 { id: "personal", title: "Personal Info", order: 1 },
29 { id: "contact", title: "Contact Info", order: 2 }
30 ]
31 })
32 );
33
34 // Change sectionVariant to "default", "card", or "collapsible"
35 const controller = new FormController(SectionSchema);
36</script>
37
38<!-- Default variant -->
39<SchemaForm {controller} sectionVariant="default" />
40
41<!-- Card variant -->
42<SchemaForm {controller} sectionVariant="card" />
43
44<!-- Collapsible variant -->
45<SchemaForm {controller} sectionVariant="collapsible" />Section Variants - Collapsible
Accordion-style collapsible sections
Custom Footer
Override the default footer using the footer snippet. The snippet
receives isSubmitting, isValid, isFirstStep, isLastStep, handleSubmit, handleNext, and handlePrev.
Code Svelte
1
2<script lang="ts">
3 import { Schema, pipe } from "effect";
4 import { SchemaForm, FormController, withField, withFormLayout } from "@kareyes/aether/forms";
5 import { Button } from "@kareyes/aether";
6
7 // --- Custom Footer ---
8 const CustomFooterSchema = pipe(
9 Schema.Struct({
10 title: pipe(
11 Schema.String,
12 Schema.minLength(1),
13 withField({ label: "Title", colSpan: 6 }),
14 ),
15 slug: pipe(
16 Schema.String,
17 Schema.minLength(1),
18 withField({
19 label: "Slug",
20 colSpan: 6,
21 description: "URL-friendly identifier",
22 }),
23 ),
24 body: pipe(
25 Schema.String,
26 withField({ label: "Body", inputType: "textarea" }),
27 ),
28 }),
29 withFormLayout({
30 columns: 12,
31 sections: [{ id: "main", title: "New Post" }],
32 }),
33 );
34
35 const customFooterController = new FormController(CustomFooterSchema, {
36 validateOnBlur: true,
37 });
38</script>
39
40 <SchemaForm
41 controller={customFooterController}
42 onSubmit={(data) => console.log("Post:", data)}
43 >
44 {#snippet footer({ isSubmitting, isValid, handleSubmit })}
45 <div class="flex items-center justify-between pt-4 border-t border-border">
46 <p class="text-sm text-muted-foreground">
47 {isValid ? "Ready to publish" : "Fix errors above"}
48 </p>
49 <div class="flex gap-2">
50 <Button
51 variant="outline"
52 type="button"
53 onclick={() => customFooterController.reset()}
54 >
55 Discard
56 </Button>
57 <Button
58 variant="outline"
59 type="button"
60 onclick={handleSubmit}
61 disabled={isSubmitting}
62 >
63 Save Draft
64 </Button>
65 <Button
66 type="button"
67 onclick={handleSubmit}
68 disabled={isSubmitting || !isValid}
69 >
70 {isSubmitting ? "Publishing…" : "Publish"}
71 </Button>
72 </div>
73 </div>
74 {/snippet}
75 </SchemaForm>